Since 2020, I manage a server to host all my fun side projects on it, but it turns out, it became a server to host multiple Wordpress for customers. At the beginning, I used Docker coupled with Nginx as reverse proxy. However, I migrated to Traefik as Reverse proxy because:
- Easy setup and config
- Automaticly generate and renew SSL certificates
- Support Docker / Swarm / Kubernetes
- Support HTTP/3
- Active and Strong open-source community
Traefik has many roles, but the main ones:
- Routing: The incoming traffic is redirected to the corresponding service based on Rules (a website, API or any service)
- Load Balancing: The incoming traffic is distributed across multiple instances (if you use Swarm/Kubernetes)
- SSL Decryption: The traffic is decrypted before it reaches the corresponding service.
- Dynamic Config: New services can be discovered, and traefik configure itself.
For instance, you start a new Wordpress container, Traefik will automatically: detect it, create certificates, create a route, and voilà, your website is accessible from internet!
Setup the Traefik Reverse Proxy
Before going any further, I assume you have a VPS or server accessible from a public IP
To start the Traefik service, create a docker-compose.yml, and write the following configuration:
version: "3.7"
services:
reverse-proxy:
# The official v3 Traefik docker image
image: traefik:latest
container_name: reverse-proxy
# Enables the web UI and tells Traefik to listen to docker
command:
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http3
- --log.level=INFO
- --providers.docker=true
- --providers.docker.network=proxy
- --providers.docker.exposedbydefault=false # Do not expose containers unless explicitly told so
- --api=true # Enable dashboard
- --api.debug=true
- --certificatesresolvers.le.acme.tlschallenge=true
- --certificatesresolvers.le.acme.email=YOUR_EMAIL # cert resolvers
- --certificatesResolvers.le.acme.storage=/ssl/acme.json
restart: always
ports:
- 80:80/tcp
- 80:80/udp
- 443:443/tcp
- 443:443/udp
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- ${PWD}/ssl:/ssl
labels:
traefik.enable: true
# Dashboard
traefik.http.routers.reverse-proxy.rule: Host(`YOUR_CUSTOM_DOMAIN`)
traefik.http.routers.reverse-proxy.entrypoints: websecure
traefik.http.routers.reverse-proxy.service: api@internal
traefik.http.routers.reverse-proxy.tls: true
traefik.http.routers.reverse-proxy.tls.certresolver: le
traefik.http.routers.reverse-proxy.middlewares: dashboard
traefik.http.middlewares.dashboard.basicauth.users: YOUR_CUSTOM_ADMIN_HTTP_PASSWORD
# SECURITY MIDDLEWARE named "hstsx"
traefik.http.middlewares.hstsx.headers.stsincludesubdomains: true
traefik.http.middlewares.hstsx.headers.stspreload: true
traefik.http.middlewares.hstsx.headers.stsseconds: 31536000
traefik.http.middlewares.hstsx.headers.forcestsheader: true
traefik.http.middlewares.hstsx.headers.customframeoptionsvalue: sameorigin
traefik.http.middlewares.hstsx.headers.browserxssfilter: true
traefik.http.middlewares.hstsx.headers.sslredirect: true
traefik.http.middlewares.hstsx.headers.contenttypenosniff: true
networks:
- default
networks:
default:
external:
name: proxy
A lot is happening here, let me detail the code:
- First, you have to replace 3 configurations to make it work:
- YOUR_EMAIL: Email used by let's encrypt to create certificates
- YOUR_CUSTOM_DOMAIN: Traefik provide a web dashboard to manage your services, you can create a dedicated domain or sub-domain for accessing the panel. The domain must link to your server public IP.
- YOUR_CUSTOM_ADMIN_HTTP_PASSWORD: To access the Traefik dashboard, you can restrict the access with credentials. Search online "htpasswd generator", generate credentials and write the value here. (Corresponding BasicAuth documentation).
- At the beginning of the file, there is a rule used to redirect all non secured traffic (port 80) to HTTPS (port 443)
- HTTP/3 is enabled by default: that's why the UDP and TCP connections are opened for both ports (80 and 443)
- You can find a special middleware applied for all websites: it adds security headers used by web browsers to enable HSTS, Redirect to HTTPS, enable X-framing to avoid click-jacking attacks, by ensuring that their content is not embedded into other sites. Finally there is
browserxssfilter
, to stop pages from loading when they detected reflected cross-site scripting (XSS) attacks. - At the end of the file, the Traefik service is only available on the Docker external network named proxy.
Before starting the service, create the network proxy
by running the command:
docker network create proxy
Then start the Traefik container:
docker-compose up -d
The Web Dashboard is now accessible from the domain specified at YOUR_CUSTOM_DOMAIN
. The UI shows all the routes, providers and services handled by Traefik (screenshot taken from the Traefik documentation):
Your dashboard should print 0 Routes, 0 Services, and 0 provider
Voilà, we covered the most important with Traefik, to learn more about each configuration, take a look at the Traefik documentation.
Setup a Wordpress Container with Traefik
This part covers how to create a Wordpress container, and make it accessible from a custom domain thanks to traefik.
Create a new docker-compose.yml and write the following configuration:
version: '3.7'
services:
wordpress-db:
container_name: ${CONTAINER_DB_NAME}
image: mariadb:latest
restart: unless-stopped
volumes:
- ${DB_PATH}:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
expose:
- ${PORTDB}
healthcheck:
test: mysql --user=${MYSQL_USER} --password=${MYSQL_PASSWORD} --database=${MYSQL_DATABASE} --silent --execute "SHOW DATABASES"
interval: 2m
timeout: 10s
retries: 5
start_period: 40s
wordpress-server:
container_name: ${CONTAINER_WP_NAME}
image: wordpress:latest
restart: unless-stopped
volumes:
- ${WP_CORE}:/var/www/html
- ${WP_CONTENT}:/var/www/html/wp-content
environment:
WORDPRESS_DB_HOST: ${CONTAINER_DB_NAME}:${PORTDB}
WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
WORDPRESS_DB_USER: ${MYSQL_USER}
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX}
VIRTUAL_HOST: ${DOMAINS}
logging:
options:
max-size: ${LOGGING_OPTIONS_MAX_SIZE:-200k}
expose:
- ${PORTWEBSITE}
depends_on:
- wordpress-db
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
labels:
traefik.enable: true
traefik.http.routers.wordpress-server.rule: Host(`${DOMAINS}`)
traefik.http.routers.wordpress-server.tls: true
traefik.http.routers.wordpress-server.tls.certresolver: le
traefik.http.routers.wordpress-server.entrypoints: websecure
networks:
- default
networks:
default:
external:
name: proxy
Code break-down:
- The file defines two services: MariaDB service for the Database, and the Wordpress service which depends on the Database.
- Volumes are used to save locally the Database, and Wordpress files.
- Two Healthchecks are made for both service to verify if every runs smoothly. If the Healthcheck fails, Traefik won't make the service accessible.
- The Wordpress service has special Traefik labels:
- traefik.enable: true: Notifies traefik to expose the container. If false, the container is ignored.
- traefik.http.routers.wordpress-server: This label is used to define rules to link a custom Domain to the Wordpress container, then it enables SSL (HTTPS), finaly the traffic must only come from HTTPS (websecure Port 443).
Before starting the Wordpress container, make sure you have defined the following environment variables in the .env file:
PORTWEBSITE=80
PORTDB=5432
#
# Database Container configuration
#
CONTAINER_DB_NAME=
# Path to store your database (Volume)
DB_PATH=/data/database_name
# Root password for your database
MYSQL_ROOT_PASSWORD=
# Database name, user and password for your wordpress
MYSQL_DATABASE=
MYSQL_USER=
MYSQL_PASSWORD=
#
# Wordpress Container configuration
#
CONTAINER_WP_NAME=
# Path to store your wordpress files
WP_CORE=./public/wp-core
WP_CONTENT=./public/wp-content
# Table prefix
WORDPRESS_TABLE_PREFIX=wp_
# Your domain (or domains)
DOMAINS=domain.org,blog.domain2.com
# Your email for Let's Encrypt register
LETSENCRYPT_EMAIL=
Now we are ready to start the Wordpress container, run the docker command:
docker-compose up -d
-d
for detached mode, it starts the container in the background and leaves it running.
Traefik may takes a couple of minutes to create an HTTPs certificate, and make the service available online from your public IP. Voilà, you created a fresh new Wordpress website 🎉.
Setup a Node Container with Traefik
This section covers how to create a Node Server container, and make it accessible from a custom domain thanks to traefik. It could be an API, a frontend, or the Email API server from my previous article.
The Docker image used is node:18-slim
to make the container small, and we are going to install CURL for the Docker healthcheck. Create a file named Dockerfile, and write the following config:
FROM node:18-slim
RUN apt-get update && apt-get install curl -y
ENV APP_ROOT /src
WORKDIR ${APP_ROOT}
ADD . ${APP_ROOT}
RUN npm install
The base image is updated to get the latest security patches, and the CURL command is installed. In a second step, the local directory is loaded into the /src container directory, and Node packages are installed.
The Node image is now ready, let's create a docker-compose.yml file, and write the following configuration:
version: "3.7"
services:
node-project:
build: .
container_name: ${DOMAIN}
restart: always
expose:
- "${PORT}"
command: "npm run start"
networks:
- default
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "http://localhost:${PORT}"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
labels:
traefik.enable: true
traefik.http.routers.node-project.rule: Host(`${DOMAIN}`)
traefik.http.routers.node-project.tls: true
traefik.http.routers.node-project.tls.certresolver: le
traefik.http.routers.node-project.entrypoints: websecure
networks:
default:
external:
name: proxy
Exactly like the previous Wordpress container, the Node container has the similar Traefik labels for: Defining a domain, enabling SSL, and it receives the traffic from HTTPS (Websecure).
The Docker-compose file requires only two environment variables on the .env file:
PORT=3000
DOMAIN=api.domain.org
Finally, start the Docker container with the command:
docker-compose up -d
Wait a couple of minutes, then your Node server is available from your public IP 🎉!
Conclusion
Note: Before writing this article, I migrated from Traefik v2 to Traefik v3, and I restarted the container with the same configuration: full backward compatibility, no errors, that's beautiful! 🤌🤌
After using Traefik as Reverse Proxy for 3 years for professional and personal use, it does a perfect job, with no maintenance, easy configuration, easy monitory and no downtime.
Feel free to write a comment if you need help or have questions with Traefik.
Have a great day, cheers 🍻
Top comments (0)