So I think I have managed to move my WP Traefik deployment from v1.7 to v2.0, so I should be save if they decide to retire the 1.7 docker image sometime in the future. Not that I see them removing v1.7 anytime soon, but should they decide to do that, I should be fine.
Anyway, here’s My swarm-friendly stack configuration:
version: "3.3"
services:
traefik:
image: "traefik:v2.2.1"
networks:
- wpnet
command:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --providers.docker=true
- --providers.docker.swarmMode=true
- --providers.docker.network=wpnet
- --providers.docker.exposedbydefault=false
- --providers.file.directory=/configuration/
- --providers.file.watch=true
ports:
- target: 80
published: 80
mode: host
- target: 443
published: 443
mode: host
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "/app/wp/traefik/configuration/:/configuration/"
deploy:
placement:
constraints:
- node.role == manager
wpdb:
image: mysql
networks:
- wpnet
volumes:
- /app/wp/db/data:/var/lib/mysql
- /etc/localtime:/etc/localtime:ro
environment:
- MYSQL_ROOT_PASSWORD=nomunomuchowa
deploy:
placement:
constraints:
- node.labels.db == true
wpapp:
image: wordpress:5.4.1-php7.2-apache
networks:
- wpnet
depends_on:
- wpdb
volumes:
- /app/wp/app/wp-content:/var/www/html/wp-content
- /app/wp/app/conf/logformat.conf:/etc/apache2/conf-enabled/logformat.conf
- /app/wp/app/conf/security.conf:/etc/apache2/conf-available/security.conf
- /app/wp/app/conf/php.ini:/usr/local/etc/php/php.ini
environment:
- WORDPRESS_DB_HOST=wpdb
- WORDPRESS_DB_NAME=journaldb
- WORDPRESS_DB_USER=wpuser
- WORDPRESS_DB_PASSWORD=dikalasibukmaupunsenggang
deploy:
replicas: 1
labels:
- "traefik.enable=true"
- "traefik.http.routers.my-app.rule=Host(`journal.mach5.web.id`) || Host(`mach5.web.id`)"
- "traefik.http.routers.my-app.entrypoints=websecure"
- "traefik.http.routers.my-app.tls=true"
- "traefik.http.services.my-app.loadbalancer.server.port=80"
- "traefik.http.services.my-app.loadbalancer.sticky=true"
- "traefik.http.services.my-app.loadbalancer.sticky.cookie.name=StickyCookie"
- "traefik.http.services.my-app.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.redirs.entrypoints=web"
- "traefik.http.routers.redirs.middlewares=redirect-to-https"
- "traefik.http.routers.my-app.middlewares=secheader"
- "traefik.http.middlewares.secheader.headers.referrerPolicy=no-referrer"
- "traefik.http.middlewares.secheader.headers.framedeny=true"
- "traefik.http.middlewares.secheader.headers.stsSeconds=31536000"
- "traefik.http.middlewares.secheader.headers.forceSTSHeader=true"
- "traefik.http.middlewares.secheader.headers.stsPreload=true"
- "traefik.http.middlewares.secheader.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.secheader.headers.browserXssFilter=true"
- "traefik.http.middlewares.secheader.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secheader.headers.featurePolicy=self"
- "traefik.http.middlewares.secheader.headers.customRequestHeaders.X-Forwarded-Proto=https"
networks:
wpnet:
external: true
A few notable entries from the config above:
TLS/SSL Certificate configuration
As you can see from the configuration above
- "traefik.http.routers.my-app.rule=Host(`journal.mach5.web.id`) || Host(`mach5.web.id`)"
I’ll publishing the blog under the URL https://mach5.web.id, and https://mach5.web.id, which can be configured with wildcard certificate, or one for each domain. To achive the later, I’ll be using Traefik’s file provider, which is configured here:
- --providers.file.directory=/configuration/ - --providers.file.watch=true
The path /configuration is configured to resolve to local directory through:
volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "/app/wp/traefik/configuration/:/configuration/"
Inside the local directory, there are a couple of files:
surfer@DM1:/app/wp/traefik/configuration$ ls -la total 44 drwxr-xr-x 2 root root 4096 Jun 8 15:15 . drwxr-xr-x 3 root root 4096 May 7 07:15 .. -rw-r--r-- 1 root root 249 Jun 8 15:15 certificates.toml -rw------- 1 root root 1679 Jun 8 15:13 journal.mach5.web.id.key -rw-r--r-- 1 root root 3574 Jun 7 15:32 journal.mach5.web.id.pem -rw------- 1 root root 1675 Jun 8 15:12 mach5.web.id.key -rw-r--r-- 1 root root 3550 Jun 8 15:11 mach5.web.id.pem -rw-r--r-- 1 root root 184 May 7 17:50 tls.toml
The file certificates.toml configure the name and path for the certificates used on the deployment:
[[tls.certificates]] certFile = "/configuration/journal.mach5.web.id.pem" keyFile = "/configuration/journal.mach5.web.id.key" [[tls.certificates]] certFile = "/configuration/mach5.web.id.pem" keyFile = "/configuration/mach5.web.id.key"
All you have to do to use multiple certificate is to rename the certificate pairs that you received from your provider in correspodence to subdomains and/or domains used on the deployment, and put the path in the toml file. To make it easier, use the extention “.key” for private key, and “.pem” or “.crt” for the fullchain certificate.
The other toml file on the directory contains the TLS configuration, I’m using to enforce the use of TLS v1.2 as a minimum version of TLS used on the deployment:
[tls.options]
[tls.options.default]
minVersion = "VersionTLS12"
cipherSuites = [
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
preferServerCipherSuites = true
Logging client’s real IP addresses
To get real client IP address logged, combine this configuration on Traefik:
ports:
- target: 80
published: 80
mode: host
- target: 443
published: 443
mode: host
And on WordPress:
volumes:
volumes:
- /app/wp/app/conf/logformat.conf:/etc/apache2/conf-enabled/logformat.conf
Which contains:
surfer@DM1:~$ more /app/wp/app/conf/logformat.conf
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
Securing The Deployment
The last bit I want to talk about is secure http headers configuration, which are contained in these lines:
- "traefik.http.routers.my-app.middlewares=secheader
- "traefik.http.middlewares.secheader.headers.referrerPolicy=no-referrer"
- "traefik.http.middlewares.secheader.headers.framedeny=true"
- "traefik.http.middlewares.secheader.headers.stsSeconds=31536000"
- "traefik.http.middlewares.secheader.headers.forceSTSHeader=true"
- "traefik.http.middlewares.secheader.headers.stsPreload=true"
- "traefik.http.middlewares.secheader.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.secheader.headers.browserXssFilter=true"
- "traefik.http.middlewares.secheader.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secheader.headers.featurePolicy=self"
- "traefik.http.middlewares.secheader.headers.customRequestHeaders.X-Forwarded-Proto=https"