Welcome to the DjaoDjin Blog!

A place to share experiences in building Software-as-a-Service.

First day with Docker

by Sebastien Mirolo on Sun, 14 May 2017

(This post was updated for running Docker on Fedora 25) Since Docker was released as open-source, it has spread like wildfire. Both Amazon and Google have been quick to support Docker on their respective cloud.

Docker is great because it implements something akin to the old times chroot, jail or LXC containers (albeit a lot more powerful), bundled with something akin to pip (or rubygem) for virtual machines.

What we are looking to achieve Today is deploy a Django webapp in a Docker container on a Fedora 25 system. The Fedora 25 system is itself boot up from a stock Fedora image on a VMware Fusion virtual machine. We will also want to reconfigure the nginx front-end load balancer to redirect requests to the Django webapp.

Enough presentation, let's dive into the setup.

Installing Docker

Following How to run Docker containers on CentOS or Fedora, the first steps are straightforward.

$ sudo groupadd docker
$ sudo dnf install docker
$ sudo systemctl start docker.service
$ sudo systemctl enable docker.service
$ sudo usermod -a -G docker $USER

Then logout and log back in.

Building a Docker container

Dockerizing a Python Web App is a little more complex than it could for a tutorial starter, yet still a good place to begin.

We will prefer to start a Django project from scratch here.

$ sudo dnf install python-virtualenv
$ virtualenv dockerized-webapp
$ mkdir -p dockerized-webapp/reps
$ cd dockerized-webapp/reps
$ source ../bin/activate
$ pip install Django
$ django-admin.py startproject myapp
$ cd myapp

$ cat Dockerfile
FROM ubuntu:12.10

# Install Python Django
RUN apt-get install -y python-virtualenv
RUN pip install Django

# Bundle app source
ADD . /src

# Expose
Expose 8000

# Run
CMD ["python", "/src/manage.py", "runserver", "0.0.0.0:8000"]

$ docker build -t myapp .

First Hick-up

Apt-get would not be able to access the Internet from with the container. There are reference to this kind of issues here and here. Debugging turned out into one of these "try random things until it works" session. It didn't help that ping was not available in the base container. Among other things I created a /etc/default/docker file as follows:

$ cat /etc/default/docker
DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"

Updated the iptables rules on the Fedora system,

Also rebooted the virtual machine ... twice.

Miraculously the container started to be able to access the Internet.

Running a Docker container

Running the docker container at this point is pretty straightforward:

$ docker run -d -p 8000:8000 myapp

I can access the myapp Django starting page through the web browser on the Mac at this point by typing the url: http://<vm_ip_addr>:8000/.

Before moving, time to familiarize ourselves with a few useful docker commands:

$ docker run -i -t myapp /bin/bash
$ docker ps -a
$ docker images
$ docker stop [container-id]

Automated Nginx Reverse Proxy for Docker

This post and the associated tools developed for it are perfect for the job.

First install and start nginx,

$ sudo dnf install nginx
$ sudo systemctl start nginx.service
$ sudo systemctl enable nginx.service

Then download the docker-gen tool

$ wget https://github.com/jwilder/docker-gen/releases/download/0.3.1/docker-gen-linux-amd64-0.3.1.tar.gz
$ tar xvzf docker-gen-linux-amd64-0.3.1.tar.gz

And create a template file

$ cat nginx.tmpl
{{ range $host, $containers := groupBy $ "Env.VIRTUAL_HOST" }}
upstream {{ $host }} {

{{ range $index, $value := $containers }}
    {{ with $address := index $value.Addresses 0 }}
    server {{ $address.IP }}:{{ $address.Port }};
    {{ end }}
{{ end }}

}

server {
    listen 80;
    server_name {{ $host }};

    location / {
        proxy_pass http://{{ $host }};
        include /etc/nginx/proxy_params;
    }
}
{{ end }}

We also need to create a /etc/nginx/proxy_params file here since we are not on Ubuntu. Fedora configures nginx quite differently here.

$ cat /etc/nginx/proxy_params
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header Host               $http_host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-Proto  $scheme;

        # proxy_redirect default;
        proxy_redirect off;

Second Hick-up

docker-gen: invalid cross-device link

Here docker-gen assumes the tempdir is on the same device as the final target file. The standard Fedora installation will put tempdir on its own partition. Fortunately docker-gen uses the standard Go util to create a temporary file and the Go library respects the TMPDIR shell variable.

We get it to work like this:

$ sudo sh -c 'TMPDIR=/etc/nginx/tmp ./docker-gen -only-exposed -watch \
    -notify "/etc/init.d/nginx reload" \
    nginx.tmpl /etc/nginx/conf.d/default.conf'

We start the docker container this way to trigger a new nginx default.conf file

$ docker run -e VIRTUAL_HOST=demo.localhost.localdomain -t myapp

We are almost there. Since we are not running any DNS server on our Fedora virtual machine, we first update /etc/hosts so we can find demo.localhost

$ diff -u prev /etc/hosts
-127.0.0.1   localhost localhost.localdomain
-::1         localhost localhost.localdomain
+127.0.0.1   demo.localhost localhost localhost.localdomain
+::1         demo.localhost localhost localhost.localdomain

Everything should be in place to be able to access the webapp within the docker container. We run the following command from within the Fedora virtual machine.

$ wget http://demo.localhost/

On a side note, looking around we found the following post to trigger the docker API while it is running on a unix socket.

echo -e "GET /images/json HTTP/1.0\r\n" | nc -U /var/run/docker.sock

Pretty awesome!

Third Hick-up

At this point we get a "502 Bad Gateway" error. The nginx logs show a permission denied.

$ tail /var/log/nginx/error.log
... (13: Permission denied) while connecting to upstream

Updating SELinux policies

Albeit in a different context, there is a great article that explains what is going on.

SELinux prevents nginx to connect to the docker container. Let's not forget that the docker containers run on a different subnet.

$ sudo dnf install policycoreutils-devel
$ sudo sh -c 'grep nginx /var/log/audit/audit.log | audit2allow -M nginx'
$ sudo semodule -i nginx.pp

Conclusion

That's it. We can now start to bring containers up and down and get the nginx redirect updated on the fly. So cool!

More to read

If you are looking for related posts, Docker on Elastic Beanstalk and building a Docker container and pushing it to Amazon EC2 Container Registry are good reads.

More technical posts are also available on the DjaoDjin blog. For fellow entrepreneurs, business lessons learned running a subscription hosting platform are also available.