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://journal.mach5.web.id, and https://journal.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"