Menu Close

Kottu Kontainers

Remember Kottu? Those were the days. Anyway,
Kottu has always been a totally open-source project, and theoretically
it is as easy as cloning the git repo and… jumping a few hoops to set it
up on your own local machine or VPS.

Except… the loops are tedious and there’s a bunch of things that could go
wrong. There must be a better way, right?

Enter Docker. It promises to solve one of the oldest complaints in
Computer Science, namely “it works on my machine”. I’ve been using Docker
at work for the past 6 months (hat tip to the wonderful Malitta Nanayakkara
for getting me the job AND teaching me the basics of Docker) and my mind has
been simply blown away by how simple and effective it is, and how I barely
have to think about dependencies and reproducible environments anymore.

I won’t get into the background of Docker and how it works, but there’s this
wonderful series by Jeff Hale if you’re so inclined.

Step 1: Clone the Repo

It’s been almost over 2 years since I last messed with the Kottu source code,
so it’s naturally no longer on my machine. I run a git clone and download the

I look through the instructions and they basically go like “run kottu.sql on
the mysql server which you *obviously* have running on your machine
(#LAMPBoise) and then copy this into your webroot, update the config, and bam!
Kottu for the whole kadey!”.

But we’re in 2019 and no one is stupid enough to run mysql and Apache on their
machines like that. I’m not even on Linux anymore! (the HORROR! 21 year old
me writing Kottu would’ve spit in disgust if he knew I use a Mac now.)

Step 2: Look around the web for a docker-compose.yml file to hijack

No one writes software anymore, we just copy paste snippets from a thousand
blog posts and StackOverflow answers, cobble it together, and pray it works.

I found two articles on this setup, one from DigitalOcean (excellent
resource) which is a bit Laravel-focused, and one from the
Geeky Platypus blog which was more generic. Kottu is custom PHP, and the
Geeky Platypus docker-compose.yml file was cleaner, so I used it as the base
with some bits pulled from the DigitalOcean guide. Note that both use nginx and
not Apache because we’re civilised human beings nowadays.

version: "2" services: webserver: image: nginx:alpine ports: - "8000:80" volumes: - .:/var/www - ./docker/nginx/site.conf:/etc/nginx/conf.d/default.conf networks: - internal - default db: image: mysql:5.7 ports: - "3306:3306" environment: MYSQL_DATABASE: kottu MYSQL_ROOT_PASSWORD: your_mysql_root_password volumes: - ./docker/dbdata:/var/lib/mysql/ - ./docker/mysql/my.cnf:/etc/mysql/my.cnf networks: - internal php: image: php:5.6-fpm build: context: . dockerfile: ./docker/php/Dockerfile volumes: - .:/var/www networks: - internal networks: internal: driver: bridge default: driver: host

Okay, there’s a bit to digest here:

We’re creating three services that work together, namely webserver, php and db.
The webserver is straight up garden variety nginx running on Alpine Linux, which
is very popular for Docker images because of the tiny size. It is exposing port
8000 (traditional Kottu port) on Localhost, and directing traffic from there
to the standard 80 port.

The root directory of the repo is being mounted on /var/www, and a nginx conf
file we have inside our Docker folder is being set as the default.conf for
nginx. More on this conf file (and networking) later.

The db service is based on a standard mysql 5.7 image1, exposing the
standard mysql port of 3306, and with some env variables for setting the
database name and root password. We also mount a conf file and a data folder
so that database data is persisted across container restarts (internal container
data is by definition ephemeral, so if we don’t do this we will lose our data
if the container is ever down’d).

The php service also has the repo root mounted to /var/www (so that that most
important mama of all files, index.php can be accessed), but there are some
additional dependencies we want to install not included in the standard
php:5.6-fpm image it is based on, so we include a custom Dockerfile (more
on that later). The DigitalOcean guide has port 9000 being exposed by the php
container and everything, but I found that was unnecessary (and the Geeky
Platypus doesn’t use it either) so we don’t use that here.

Step 3: Networking

I’ve skipped talking about networking because networking in Docker deserves its
own blog post, really. The example in the setup we have above, though, is a good
starting point to talk about the amazeballs planet that is Docker networking.2

Now, it is standard knowledge that not all services should be exposed to the
public internet. Ideally, we’d only expose things like SSH and the webserver
to incoming traffic. Everything else traditionally sits behind a firewall. With
this Docker Compose setup though, we have created two different networks which
serve our purpose much better than any finicky firewall rules would.


Internet traffic can enter the default network and hence
nginx, but it cannot enter the internal network.

In our dual-network setup, internet traffic can access nginx through the external
(default) network (which uses the host driver, which allows it to bind directly to the Docker host’s network), but cannot access the internal
bridge network. nginx, which sits on both the default and internal networks, can
use the internal network to communicate with php and mysql.

Note that this works for HTTP traffic (as our nginx container only exposes port
80), but on the server you might use something like
docker-letsencrypt-nginx-proxy-companion to make life easier (and support
HTTPS). It takes care of SSL certs and all that. You will have to replace the
default network in the compose file with the nginx-proxy network, attach the
webserver service to it, and add the VIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAIL env variables to make proxy companion pick up the hostname
and the certificates you need.

Step 4: PHP Dockerfile

We need to install the mysql client and the PDO_Mysql PHP extension on the php
service, so we’ll create a Dockerfile for that.

FROM php:5.6-fpm # install dependencies
RUN apt-get update && apt-get install -y mysql-client # install php extensions
RUN docker-php-ext-install pdo_mysql

I later found out that none of the images on Kottu were showing up and it’s because
Timthumb (yes, Kottu still uses Timthumb, please don’t hack us!) needs GD, which
wasn’t installed. So let’s add those lines to the file too:

# Install GD and it's dependencies RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
&& docker-php-ext-install -j$(nproc) iconv \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd

Step 5: The mysql and nginx conf files

my.cnf is so basic that it doesn’t even deserve mentioning, but obviously you can
add more configurations here:

general_log = 1
general_log_file = /var/lib/mysql/general.log

The site.conf file for nginx is your standard nginx + php site config:

server { listen 80; index index.php index.html; server_name localhost; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /var/www; location / { try_files $uri $uri/ /index.php; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass php:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; }

It tells nginx to listen for traffic on port 80, make /var/www the web root, try to
find static files for the paths that visitors are trying to access, and then redirect
those requests to index.php if that fails, and pass php requests to php:9000 (which
means port 9000 in the php service).

Step 6: Rock ’n’ Roll

We now get to say the magic words:

docker-compose up -d

Containers Starting

Ah, satisfaction…

After the images and dependencies are downloaded and the containers are set up, a
docker ps3 will reveal that three new containers have spun up and we’re ready
to roll. We direct our web browser to localhost:8000, and…

Database Issue

Victo- oh god!

Whoops, we still need to import the database. But fear not, that’s pretty easy.

docker-compose exec php bash
root@3836dc148c6a:/var/www/html# cd ..
root@3836dc148c6a:/var/www# ls
LICENSE cache config.php docker docker-compose.yml html img index.php kottu.sql lib static templates webcache
root@3836dc148c6a:/var/www# mysql -h db -u root -pyour_mysql_root_password kottu < kottu.sql

I just bash’d into the container, ran the mysql client4 (remember installing that
via the Dockerfile earlier?) and redirected kottu.sql (which holds the database
schema) into it. Now, things still look empty but not broken:

Empty Kottu

Did you guys know that Flickr still exists?

Step 7: Add a blog and fetch its posts

Let’s go to the Kottu Baas admin interface, which most people haven’t seen. Get
ready for some Web 2.0 super secure shit, yo.

Admin Login

Like all great login forms from 2012, the password is
SHA-1 hashed on the clientside itself! SEKURITEEH.

After entering the default username/password combo of indi/indi, we are inside
the uh-may-zin admin interface:

Admin Interface

We click “add a blog” and a POPUP shows up! How wizard is that?

Adding Blogs

The copyright being stuck in 2012 is just apt!

In my bout of narcissism, I add my own blog and navigate to
localhost:8000/admin/feedget/<secretkey>, and voila:

Fetching Feeds

backendsecretkeywithunicorns! SEKURITEEH.

Aaaand, finally:

Kottu working

Even Timthumb works, uguise. :’)

And there we have it. Now you’ve got your own piece of 2012 tech running a
personalised version of Kottu on Docker! Next week, we cover how Kubern—5

* * *

Okay, this post isn’t the most comprehensible piece of writing I’ve done. It
feels rushed and under-explained, and yet resulted in a very long post. The
topics covered honestly require blog posts of their own, and time permitting,
I would do one or two over the course of the year. But here’s a start. And it
feels like a Friday night (and half of Saturday) well-spent setting up an ancient
piece of code on Docker6, so that it becomes at least a tad more accessible.

I think I need to attach a fair warning: DON’T RUN THIS IN PROD, FOLKS. It’s
from 2012 and has security holes the size of the Mariana Trench in it, and it’s
a miracle that still continues to run7.

Originally Posted on Medium

  1. Another really cool thing about Docker that I missed mentioning in the post
    is the ability to run different versions of, say, mysql or php (as your apps
    need them) on different containers. This is really helpful if you have a bunch
    of old code that can only run on an older version of php/python/whatever… 

  2. Docker networking is seriously amazing, and they do things like adding
    iptables records to make it possible. I would recommend reading the
    Network section of the Docker docs. 

  3. Docker has some really cool commands that help you administer everything.
    Some of my favourites are:

    docker ps — lists all the running containers

    docker stats — container stats

    docker-compose logs -f — logs from all the containers in the compose file

    docker network ls — lists networks 

  4. A sharp-eyed reader might ask: “why not just run the mysql client from the
    exec command?”. Well, I tried that and it gives some weird tty error that
    I remember once solving, but I forgot how. This bash/mysql method described
    here works without issue. 

  5. This is a joke. I don’t know Kubernetes. Who does, really? ¯\_(ツ)_/¯ 

  6. I updated the repo and its README with how to do a Docker setup! 

  7. Hat tip to the mysterious maintainer who set up nginx and LetsEncrypt SSL
    certs on the Kottu server!

    Update: Turns out it was trusty ol’ Malinthe

%d bloggers like this: