Why Docker and Traefik
Recently I decided to redeploy my home-server. It was still running Ubuntu 12.04 and the configuration drifted ever since it was setup. Time for a revamp. I opted to use Docker and Traefik because I wanted a setup that was easy to update and easy to maintain.
All the applications I use are available on Docker Hub. Keeping all configuration in a single GIT repo using docker-compose makes it easy to maintain.
I also decided to introduce a reverse proxy, this allows all my applications to be available by entering a domain name (no more remembering ports!). Docker and Traefik combined make that easy. On top of that it’s also very easy to add SSL due to Traefik’s the Let’s Encrypt integration.
In this blog post we will be building this using Docker and Traefik:
flowchart LR
isp(("router")) --80, 443 to traefik--> traefik
subgraph host["Linux docker host"]
traefik["proxy - traefik"]
c1[Container_a]
c2[Container_b]
c3[container_x]
end
traefik --app_a.your.domain--> c1
traefik --app_b.your.domain--> c2
traefik --app_x.your.domain--> c3
traefik --redirect http to https --> isp
What distribution to choose
For my home-server I opted to use Alpine Linux because it’s lightweight and an excellent docker host. For this blog post I’ll be using Amazon Linux so it’s easy for you to follow along. However, you can use any other distro that you are happy with.
Docker host and firewall rules
We will start by spinning up a new host OS, next we will setup firewall rules. Depending on what you’re using instructions might slightly differ. The instructions below are for AWS.
Start a new host-os, I’m using Amazon Linux:
We should open the following ports in the firewall:
- 22 (to SSH into the machine, you should trim this down to your own IP).
- 80 (for HTTP traffic, required for requesting the SSL Certificate from Let’s Encrypt.
- 443 (for HTTPS)
Creating the DNS record set
We want our Docker apps to be available over the internet and we are going to setup HTTPS. Hence, we need to own a DNS domain. In order for Traefik to request certificates for the domain, we need to set up DNS first. We can either create A-Records, or use a CNAME. I will use the following two CNAME records.
*.dockerdemo.yourdomain
This will be used by containers (e.g.app1.dockerdemo.javydekoning.com
)dockerdemo.yourdomain
This will be used by Traefik (This is NOT required, we could also usetraefik.dockerdemo.javydekoning.com
)
From the instance/VM we just created, copy either the public IP (to be used in an A-Record), or the Public DNS name (to be used in a CNAME record). I will be using the public DNS name, therefore I’ll create a CNAME record.
Instructions will differ depending on your DNS provider. My zone is hosted in Route53, so we can create a record by clicking “Create Record Set” in the AWS console. We use a wildcard (*) record because we plan to create multiple sub-domains for our containers.
Configuring the Docker host
The OS only needs to run Docker and Docker-Compose all the other applications will run in containers. Let’s set that up. We’ll install docker, docker-compose and make sure the docker daemon is started on system boot.
#Install dependencies
sudo su
yum update -y
yum install -y python-pip docker
pip install docker-compose
#Start docker and configure to start on init.
service docker start
chkconfig docker on
#create files and directories
mkdir /dockerdemo/traefik -p
touch /dockerdemo/docker-compose.yml \
/dockerdemo/traefik/traefik.toml \
/dockerdemo/traefik/acme.json
chmod 600 /dockerdemo/traefik/acme.json
Next, we will create the required files and folders. That will look like this:
/dockerdemo
├── /traefik
│ ├── /traefik.toml <-- config
│ └── /acme.json <-- certificate store
└── docker-compose.yml <-- container infrastructure
Create and set permissions:
#create files and directories
mkdir /dockerdemo/traefik -p
touch /dockerdemo/docker-compose.yml \
/dockerdemo/traefik/traefik.toml \
/dockerdemo/traefik/acme.json
chmod 600 acme.json
Creating a Traefik container
First we setup our Traefik container. This container will act as a reverse proxy and redirect the traffic to the correct container. For this we will have to set up two files:
/dockerdemo/docker-compose.yml
This will tell docker what containers to run/dockerdemo/traefik/traefik.toml
This will hold the Traefik config
traefik.toml
We will start with the traefik config file. We want to configure the Traefik webinterface [web]
and re-direct all http traffic to https to encrypt all our traffic. Finally, when we create a new docker container we don’t want to reconfigure Traefik, hence we tell Traefik to watch Docker.
Open traefik.toml
in the editor of your choice and edit as follows:
#Comment out when done.
logLevel = "DEBUG"
defaultEntryPoints = ["http","https"]
[web]
#Run Traefik info page at 8080.
address = ":8080"
[entryPoints]
[entryPoints.http]
#redirect ALL http traffic to https 443
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
#Let's encrypt setup
[acme]
email = "info@javydekoning.com"
storage = "acme.json"
entryPoint = "https"
#When new host is created, request certificate.
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
#Watch Docker, when new containers are created with label create mapping.
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "dockerdemo.javydekoning.com"
watch = true
docker-compose.yml
Next we will setup our docker-compose.yml
file. We will start with just the Traefik container.
version: '3'
services:
traefik:
image: traefik
container_name: traefik
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "./traefik/traefik.toml:/traefik.toml"
- "./traefik/acme.json:/acme.json"
labels:
- "traefik.port=8080"
- "traefik.frontend.rule=Host:dockerdemo.javydekoning.com"
Some key pointers:
- docker.sock we map the sock file from the host container, so Traefik can monitor changes in the docker environment.
- ./traefik/* maps the configuration file and certificate store from our host to our Traefik container.
- traefik.port tells traefik to which backend port traffic needs to be redirected.
- traefik.frontend.rule tells traefik which ‘Host header’ should be redirected to this container.
Testing
Make sure you are in the directory of your docker-compose.yml
and run
/usr/local/bin/docker-compose up -d
Now go to the IP of your container host to see if it works. You should see:
Don’t worry about the 404. This is what we expect! Remember, we told Traefik ONLY to redirect traffic to our container if the http host header is ‘dockerdemo.yourdomain’. Let’s test that as well, you should be redirected to HTTPS and see:
If you are presented with an invalid certificate, troubleshoot using:
docker logs traefik
Adding two more containers
Finally we will introduce two more containers.
- Portainer lightweight management UI for your docker environment.
- jwilder/whoami simple HTTP docker service that prints it’s container ID.
For the whoami container we will also add a form of authentication. You can skip this if you don’t want that, or you can use the hash I generated (for testing only).
We will generate a secure Bcrypt hash. On your local machine install the apache2 utils and run:
htpasswd -nbB -C 13 username YouShouldNotUseThisPassword!
This should return:
username:$2y$13$dSXnrizi74xsfKH740Axt..tIiCL16GGpQn4hpX6Wtph15XGVS08u
You need to duplicate the ‘$’ in this string when adding this to your docker-compose.yml file.
Let’s complete our docker-compose.yml file:
version: '3'
services:
traefik:
image: traefik
container_name: traefik
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "./traefik/traefik.toml:/traefik.toml"
- "./traefik/acme.json:/acme.json"
labels:
- "traefik.port=8080"
- "traefik.frontend.rule=Host:dockerdemo.javydekoning.com"
portainer:
container_name: portainer
image: portainer/portainer:latest
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- ./portainer:/data
labels:
- "traefik.port=9000"
- "traefik.frontend.rule=Host:portainer.dockerdemo.javydekoning.com"
whoami:
container_name: whoami
image: jwilder/whoami
labels:
- "traefik.port=8000"
- "traefik.frontend.rule=Host:app1.dockerdemo.javydekoning.com"
- "traefik.frontend.auth.basic=username:$$2y$$13$$dSXnrizi74xsfKH740Axt..tIiCL16GGpQn4hpX6Wtph15XGVS08u"
There is one newly added label:
- traefik.frontend.auth.basic tells traffic to use basic authentication to authenticate a user before passing traffic on to the container.
Final Docker and Traefik test!
Make sure you are in the directory of your docker-compose.yml
file again and run
/usr/local/bin/docker-compose up -d
This should bring up the two remaining containers. Now the first thing you should do is go to portainer.yourdomain and SET the initial credentials.
Next go to app1.yourdomain, and you should be asked for the credentials that you’ve created with the ‘htpasswd’ tool. If you’ve used my exact example that would be
- user: username
- pass: YouShouldNotUseThisPassword!
Now you should see this:
All done
Congrats, you’ve configured Docker and Traefik (reverse-proxy), Docker-Compose, ssl and authentication!