My docker playground - Wed, Mar 25, 2020
For quick experiments, like trying out new software, and also for easily managing static website services for sites like this one, I run docker on a number of hosts (usually VM’s or bare metal servers). It’s quick and dirty, but it does the job, so a collegue asked me to write up a bit about this docker playground. Here it goes.
NB. This step by step guide assumes we’re using Debian 10 Linux on amd64 architecture. It may also be applicable on other systems, but I have not tested it. Let me know what you find!
The nessecary ingredients
- A linux host with root access
- DNS to add some records for this host
- Public IP
- docker
- portainer
- traefik
Let’s get cooking…
Install docker-ce
This is (almost) exactly as described at https://docs.docker.com/install/linux/docker-ce/debian/ but I am assuming you are in a root shell (i.e. not using sudo in this case)
Install required packages for using an https repo (why this isn’t available by default these days…)
apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common
Install the repository key
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
Add the docker-ce repository. I’ve chosen not to take the standard install practice here because I like custom added repo’s to not be in source.list but in sources.list.d with a separate file. This makes things like housekeeping and ansible scripting a bit easier IMO.
echo \
"deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable" > /etc/apt/sources.list.d/docker-ce.list
Update package cache and install docker-ce
apt-get update && apt-get install docker-ce docker-ce-cli containerd.io
And this step is done. If all is well, you can now run the docker ps
command to see if the docker environment is up and running.
Make it a swarm
I want this approach to be able to scale over multiple hosts if needed, so let’s make this machine it’s own Swarm. I’m assuming you know how to find your host’s IP address, you need to enter it here.
docker swarm init --advertise-addr <Host-IP>
In case you might want to add docker nodes to this swarm later on, it is usefull to save the token that the command above prints out. It looks something like this:
Swarm initialized: current node (89n3329ctbpbad0qqaw5udtnp) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3wuam4bzp5h9yvd1elbabgh16vg86tok2qommjbwqfmif7ds4d-bs1go083dm20rylz2ur1g3fid <Host-IP>:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Install portainer
The nice folks at Portainer have decent getting-started docs at https://www.portainer.io/installation/ however, I wanted a little something special. I’ve taken their portainer-agent-stack.yml and adjusted it in order to prepare for using Traefik further down the road.
Here is what it looks like now:
version: '3.2'
services:
agent:
image: portainer/agent
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
networks:
- agent_network
deploy:
mode: global
placement:
constraints: [node.platform.os == linux]
portainer:
image: portainer/portainer
command: -H tcp://tasks.agent:9001 --tlsskipverify
ports:
- "9000:9000"
volumes:
- /opt/portainer:/data
networks:
- agent_network
# - proxy
deploy:
mode: replicated
replicas: 1
placement:
constraints: [node.role == manager]
labels:
traefik.enable: "true"
traefik.http.routers.portainer.rule: Host(`<myhostname>`)
traefik.http.routers.portainer.entrypoints: https
traefik.http.routers.portainer.tls.certresolver: letsencrypt
traefik.http.services.portainer.loadbalancer.server.port: 9000
traefik.docker.network: proxy
networks:
agent_network:
driver: overlay
attachable: true
# proxy:
# external: true
There are two things to mind:
- Make sure the volume exists (in this example /opt/portainer)
- Enter the
<myhostname>
. This should be a resolvable name, because in the next steps Traefik will be trying to generate Letsencrypt certificates for your system, including the Portainer WebUI.
As you can see I’ve left port 9000 open so we can access the system before Traefik is up and running. Once we’re done, we can comment that out. Also, the network port
is commented out, once Traefik is up and running, we will enable it for this stack too.
Save the contents to a file, let’s call it portainer-agent-stack.yml
also lets create a little script to pull the images and (re)start Portainer, we’ll call upgrade_portainer.sh
Here is what that looks like:
#!/bin/bash
# Upgrade local portainer image
docker pull portainer/portainer
docker pull portainer/agent
# Stop portainer
docker stack rm portainer
# Start upgraded portainer
docker stack deploy --compose-file=portainer-agent-stack.yml portainer
Make it executable and run it
chmod +x upgrade_portainer.sh && ./upgrade_portainer.sh
If all is well you now have an operational Portainer stack which can be accessed via http://myhostname:9000 *** Insert image ***
Create your admin user now, and log in. You can now open the primary swarm and view the running stacks. There should be one: portainer
Install Traefik
We will now add Traefik as a second stack in the swarm. First create the proxy overlay network
docker network create -d overlay proxy
Also create some directories for storage.
mkdir -p /opt/traefik/letsencrypt
mkdir -p /opt/traefik/etc/conf.d
You can configure Traefik almost entirely with environment variables, but I’ve chosen not to do so because it gives me options to proxy for other stuff than just docker. You might not need to, but it won’t hurt.
Create a basic configuration in /opt/traefik/etc/traefik.yml:
# /etc/traefik/traefik.yml
global:
checkNewVersion: true
sendAnonymousUsage: true
log:
level: DEBUG
api:
insecure: true
ping: {}
entryPoints:
http:
address: :80
https:
address: :443
certificatesResolvers:
letsencrypt:
acme:
email: <admin-email>
storage: /letsencrypt/acme.json
tlschallenge: true
providers:
docker:
endpoint: unix:///var/run/docker.sock
exposedByDefault: false
swarmMode: true
file:
directory: /etc/traefik/conf.d/
watch: true
And add the ‘redirect to https’ file in /opt/traefik/etc/conf.d/redirect.yml:
# /etc/traefik/dynamic/redirect.yml
http:
routers:
redirect-to-https:
entryPoints:
- http
middlewares:
- redirect-to-https
rule: HostRegexp(`{any:.+}`)
service: redirect-to-https
services:
# bogus service, the URL will be never called
redirect-to-https:
loadBalancer:
servers:
- url: http://192.168.0.1
middlewares:
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
Almost there!
In the Stacks list, click the + Add stack button. You’ll need to give your stack a name, I suggest ’traefik’. In the Web editor, paste the compose file below.
version: "3.7"
networks:
proxy:
external: true
services:
traefik:
image: "traefik:latest"
command:
- "--configFile=/etc/traefik/traefik.yml"
ports:
- "80:80"
- "443:443"
networks:
- proxy
volumes:
- "/opt/traefik/etc:/etc/traefik"
- "/opt/traefik/letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
deploy:
placement:
constraints:
- node.role == manager
labels:
traefik.enable: "true"
traefik.http.routers.traefik.rule: Host(`traefik.<myhostname>`)
# traefik.http.routers.traefik.middlewares: auth
# traefik.http.middlewares.auth.basicAuth.users: "florian:$$apr1$$9<hash>"
traefik.http.routers.traefik.entrypoints: https
traefik.http.routers.traefik.tls.certresolver: letsencrypt
traefik.http.services.traefik.loadbalancer.server.port: 8080
traefik.docker.network: proxy
When done, click the Deploy stack button, and if all is well, Traefik will start up and generate the first Letsencrypt certs for the Portainer admin UI and for it’s own dashboard.
Wrapping up
Now we can do a few closing operations:
Lock down the unsecure Portainer port
In the portainer stack file, comment out the port snippet:
# ports:
# - "9000:9000"
and uncomment the proxy network snippets
networks:
- agent_network
# - proxy
deploy:
...
networks:
agent_network:
driver: overlay
attachable: true
proxy:
external: true
then call the upgrade_portainer.sh script.
Lock down the unsecured Traefik dashboard
The Traefik dashboard is available but unprotected. This is clearly not ideal, so you might want to set a password for it. Luckily this can be done with Traefik middlewares, as documented at https://docs.traefik.io/v2.0/middlewares/basicauth/ You need to create an htpasswd entry:
echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g
then add it to the Traefik stack by uncommenting the lines to look something like this:
traefik.http.routers.traefik.middlewares: auth
traefik.http.middlewares.auth.basicAuth.users: "florian:$$apr1$$9<hash>"
and click Update the stack
Closing thoughts
As said this method can also work across a swarm with multiple nodes, but then you might need to arrange some form of shared storage. I’ve done this with an NFS mount that was available in the same place on all docker nodes. Simply adjust the path you’re using in your stack file appropriately.
I’m sure this approach can still be improved in a myriad of ways. If you have any suggestions, please let me know :-)
\O/
That’s it! You should now have a fully functional docker/portainer/traefik box to tinker with. Enjoy!