My server setup: reverse-proxy and free SSL on Docker thanks to Traefik


  • Do not install anything but Docker on the server
  • Use Docker to run applications
  • Use docker-compose to keep application separated in stacks
  • Use Traefik 1.7 as a reverse https proxy
  • Use Portainer as web UI for Docker
  • Keep images in running containers up-to-date with watchtower
  • Use some bash aliases to speed up your common operations

How we structured our server and why

I have a shared VPS with a couple of friends. We host on it some web services and we use it to test and deploy small projects, both private and public.

How to keep a server clean and tidy

Since the server is shared between three people and that we also have applications on the server that need some continuity, for example our sites or some Telegram bots, we impose ourselves to keep it clean and tidy.


Then we decide to use Docker to ship everything, even for testing.

Docker compose

docker-compose is a tool for running multi-container Docker applications from a YAML file.

TLS, please

Chrome and Firefox and most of the browsers have deprecated unsecured http websites.

Yes, but I need a UI too!

Of course. Portainer is a great UI for Docker. We use it primarily to inspect the status, logs, and configs of the running containers, but you can use it to run containers too.

Keep containers online and up to date, without losing data

Every time there is a new version of the image used in one of your containers, watchtower pulls it and restarts gracefully a new container with the newer image.


Install docker

Just follow the official documentation.

Install docker-compose

I’ve built this simple bash script to install and update docker-compose. Unfortunately, the update must be done manually, but just running this script is enough to replace the old version with the new one.

#!/bin/bash# get latest docker compose released tag
COMPOSE_VERSION=$(curl -s | grep 'tag_name' | cut -d\" -f4)
# Install docker-compose
sh -c "curl -L${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose"
chmod +x /usr/local/bin/docker-compose
sh -c "curl -L${COMPOSE_VERSION}/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose"
# Output compose version
docker-compose -v

Run Traefik, Portainer, and watchtower

First, create a directory for the Portainer data. This folder will be mounted as a volume to the container so that the configurations will remain.

mkdir portainer-data
defaultEntryPoints = ["https","http"][entryPoints]
address = ":80"
entryPoint = "https"
address = ":443"
endpoint = "unix:///var/run/docker.sock"
watch = true
network = "traefik"
# prevent exposing each container by default
# you'll need to add the `traefik.enable=true` label to containers
#exposedByDefault = false
# will expose all your containers on <container-name>.<your.domain>
# override adding to containers the label
# `trafik.frontend.rule=Host:<your.other.domain>`
email = "<your-email>"
storage = "/etc/traefik/acme/acme.json"
entryPoint = "https"
onHostRule = true
entryPoint = "http"
version: '3.7'services:
image: traefik:v1.7
container_name: traefik
restart: always
- "80:80"
- "443:443"
- default
- traefik
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik.toml:/etc/traefik/traefik.toml
- ./acme:/etc/traefik/acme
- traefik.frontend.headers.customResponseHeaders=Access-Control-Allow-Origin:*
stdin_open: true
tty: true
image: portainer/portainer:latest
container_name: portainer
restart: always
- /var/run/docker.sock:/var/run/docker.sock
- ./portainer-data:/data
- traefik.frontend.rule=Host:portainer.<your-domain>
stdin_open: true
tty: true
image: v2tec/watchtower:latest
container_name: watchtower
restart: always
- /var/run/docker.sock:/var/run/docker.sock
stdin_open: true
tty: true
name: traefik
docker-compose up -d

Ship and run your application under https

To expose your container with a public domain, you only need to apply this label on it.

version: "3.7"services:
image: mongo # the MongoDB image from Docker Hub
container_name: mongodb # default name: myapp_db_1
restart: always # restart the container if crashes
# will mount the db-data dir inside the cwd as volume
- ./db-data:/data/db
# build an image using the files and the Dockerfile in ./server
build: ./server
image: myapp/server # name of the built image (optional)
container_name: server
restart: always
depends_on: # starts after the db
- mongo
networks: # attach the container to the traefik network,
- traefik # so that traefik can route traffic to it
- default # attach it also to the default network to see db
# define the domain associated to this service
# DNS must point to your server
image: nginx
container_name: frontend
restart: always
# mount the html as read-only (least privilege principle)
- ./frontend:/usr/share/nginx/html:ro
# we don't need the default network, frontend its stand-alone
- traefik
# assign two domains to this service, the first is the primary
# link the traefik network created in the traefik's docker-compose
name: traefik

Bash aliases with some shortcuts

In the repository, you will find also a script that installs this bash aliases.

alias down="docker-compose down"
alias up="docker-compose up -d"
alias run="docker run -dit --restart=always"
alias stop="docker rm -f"
alias logs="docker logs"


That’s all, thank you for your attention. I hope that was useful. If so, please clap your hands 👏



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Aldo D'Aquino

Aldo D'Aquino

🔗 — 👨‍💻 Infrastructure Engineer @ BendingSpoons