May's Blog

I’m not a very big fan of WordPress. In my honest opinion main problem (and benefit?) is that WordPress has many plugins.It is like two sided sword where on one side you have plugins for almost everything and on other side is quality of those plugins. Some plugin are good supported by their creators to ensure copatibility with latest version of Wordpress and sometimes just one plugin on your page blocking you from upgrade to a new version because the developer not updating it anymore.

Even worse is if your super-cool theme which you bought is using one if this plugins (besides another 20 which are needed to ensure your theme to run). Maybe you dear reader is one of those creators who using many plugins in you theme, I want to give u an advice to pay more attention which plugin you use as dependency in your theme.

Ok back to configuring Wordpress on docker. After we end, your server will run everything bellow with just one command.

What you will need? (Prerequisites)

If you want host from home you have to have static external IP address, and properly setup NAT on your router. Another way but more advanced is create VPS with some cloud provider and create VPN network, then you can forward requests to your server from proxy to home server over VPN. Ok Back to docker…

If you dont have installed docker and/or docker-compose you can find it Here for Docker and here is docker-compose.

At first Networks:

Let’s start by creating docker-compose file:

1version: "3.9"
2
3networks:
4  frontend:
5    external: true
6  wordpress_backend:
7    internal: true

We will have two networks:

External networks are not created automaticaly with docker compose so create it manually:

1docker network create frontend

Services

 1version: "3.9"
 2
 3networks:
 4  frontend:
 5    external: true
 6  wordpress_backend:
 7    internal: true
 8
 9services:
10  # here goes services

Traefik

At first, add Traefik service. This is our reverse proxy. It allows connecting to our app, upgrading HTTP to HTTPS and it can request SSL certificates for services behind him.

 1wordpress_traefik:
 2  image: traefik
 3  command:
 4    --configFile=/traefik.yml
 5  restart: unless-stopped
 6  networks:
 7    - frontend
 8    - wordpress_backend
 9  ports:
10    - "80:80"
11    - "443:443"
12  volumes:
13    - /etc/localtime:/etc/localtime:ro
14    - /var/run/docker.sock:/var/run/docker.sock:ro
15    - ./traefik.yml:/traefik.yml:ro
16    - ./tls_config.yml:/tls_config.yml:ro
17    - ./letsencrypt:/letsencrypt
18  environment:
19    - CF_API_EMAIL=<your-email>
20    - CF_API_KEY=<your-cloudflare-api-key>

I’m using DNS challenge to renew and generate certificates but there are more Supported provider.

Im using cloudflare for most of my domains, so if you want to configure traefik to use it as your DNS provider addd following code to your traefik.yml file

 1# traefik.yml
 2# ...
 3certificatesresolvers:
 4  le:
 5    acme:
 6      httpchallenge:
 7        entrypoint: "web"
 8      emaiL: "<change to your email>"
 9      storage: "/letsencrypt/acme.json"
10      dnschallenge:
11        provider: "cloudflare"
12# ...

Another important configuration is to disable unsupported versions of TLS and SSL. By default traefik has enabling to use each version of SSL and TLS. This preset can be changed by following code.

 1# tls_config.yml
 2tls:
 3  options:
 4    mytls:
 5      sniStrict: true
 6      minVersion: VersionTLS12
 7      cipherSuites:
 8        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
 9        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
10        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
11        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
12        - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
13        - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
14      curvePreferences:
15        - CurveP521
16        - CurveP384
17    mintls13:
18      minVersion: VersionTLS13

With this config you can get “A+” rank for your server with SSL Labs test

Wordpress Web

I using an FPM image for WordPress and as server I’m using nginx. First of all add another service configuration for your NGINX to your docker-compose.yml file as follows

 1wordpress_proxy:
 2  image: nginx:alpine
 3  restart: unless-stopped
 4  depends_on:
 5    - wordpress_app
 6    - wordpress_db
 7  volumes:
 8    - ./nginx.conf:/etc/nginx/nginx.conf:ro
 9  volumes_from:
10    - wordpress_app
11    - wordpress_traefik
12  networks:
13    - wordpress_backend

Next I need to tell Traefik some more about services I have in my docker-compose file (where can it find my server and how to connect to it). This can be ensured by adding labels to each service which have to be accessible from internet trough Traefik proxy. Here is example for my NGINX service.

1# ... paste this after network from example above
2labels:
3  - "traefik.enable=true"
4  - "traefik.http.routers.wordpress.entrypoints=web,websecure"
5  - "traefik.http.routers.wordpress.rule=Host(`<change to your web address>`)"
6  - "traefik.http.routers.wordpress.tls.certresolver=le"
7  - "traefik.http.routers.wordpress.tls.options=mytls@file"
8  - "traefik.http.routers.wordpress.service=wordpress"
9  - "traefik.http.services.wordpress.loadbalancer.server.port=80"

Nginx is used as HTTP server for PHP-FPM image which contains my Wordpress website. But before this you have to configure it. Create nginx.conf file and add there following code.

 1# nginx.conf
 2user nginx;
 3
 4events {
 5  worker_connections 768;
 6}
 7
 8http {
 9  upstream backend {
10    server wordpress_app:9000;
11  }
12
13  include /etc/nginx/mime.types;
14  default_type application/octet-stream;
15  gzip on;
16  gzip_disable "msie6";
17
18  server {
19    listen 80;
20
21    root /var/www/html/;
22    index index.php index.html index.htm;
23
24    location / {
25      # try_files $uri $uri/ =404;
26      try_files $uri $uri/ /index.php?$args;
27    }
28
29    error_page 404 /404.html;
30    error_page 500 502 503 504 /50x.html;
31    location = /50x.html {
32      root /usr/share/nginx/html;
33    }
34
35    location = /favicon.ico {
36      log_not_found off;
37      access_log off;
38    }
39
40    location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
41      access_log off; log_not_found off; expires max;
42    }
43
44
45    location ~ [^/]\.php(/|$) {
46      fastcgi_split_path_info ^(.+?\.php)(/.*)$;
47      if (!-f $document_root$fastcgi_script_name) {
48        return 404;
49      }
50      # This is a robust solution for path info security issue and works with "cgi.fix_pathinfo = 1" in /etc/php.ini (default)
51
52      include fastcgi_params;
53      fastcgi_index index.php;
54      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
55      fastcgi_param PHP_VALUE "upload_max_filesize=64m
56      post_max_size=64m";
57      fastcgi_pass wordpress_app:9000;
58    }
59  }
60}

Wordpress

Add WordPress service. I am using the FPM version. WordPress will need to access to the internet so add it to both networks frontend for internet access and wordpress_backend for the ability to connect to the database. We don’t need traefik here so disable it by add - "traefik.enable=false" to labels.

 1wordpress_app:
 2  image:  wordpress:5-php8.0-fpm-alpine # wordpress:5-fpm-alpine
 3  restart: unless-stopped
 4  depends_on:
 5    - wordpress_db
 6  networks:
 7    - wordpress_backend
 8    - frontend
 9  environment:
10    WORDPRESS_DB_HOST: wordpress_db
11    WORDPRESS_DB_USER: exampleuser
12    WORDPRESS_DB_PASSWORD: examplepass
13    WORDPRESS_DB_NAME: exampledb
14  volumes:
15    - wordpress:/var/www/html
16  labels:
17    - "traefik.enable=false"

Database

WordPress needs to MySQL database to run. So add it (MariaDB is also supported). Environment Variables must correspond with the WordPress DB variable. WordPress will create all database tables at the first run.

 1wordpress_db:
 2  image: mysql:8.0.21
 3  restart: unless-stopped
 4  command: --default-authentication-plugin=mysql_native_password
 5  networks:
 6    - wordpress_backend
 7  environment:
 8    MYSQL_DATABASE: exampledb
 9    MYSQL_USER: exampleuser
10    MYSQL_PASSWORD: examplepass
11    MYSQL_RANDOM_ROOT_PASSWORD: '1'
12  volumes:
13    - db:/var/lib/mysql

Adminer

This is mostly for development when you need to look inside the database.

1adminer:
2  image: adminer
3  depends_on:
4    - wordpress_db
5  networks:
6    - frontend
7    - wordpress_backend
8  restart: always

Volumes

Volumes to store your data. You can map host folders instead of volumes, then you don’t need the following lines of code. Your docker-compose file wi have the following structure.

 1version: "3.9"
 2
 3networks:
 4  # your networks
 5
 6services:
 7  # your services
 8
 9# Add following content at the end
10volumes:
11  wordpress:
12    driver: local
13  db:
14    driver: local

Folder sturcture

Your folder has to contain the following files

1docker-compose.yml
2nginx.conf
3tls_config.yml
4traefik.yml

Start it

Ok let’s start our Wordpress

1docker-compose up -d

And navigate to your address. WordPress will ask you to create a new admin user and password.

Delete it

If you need to delete it you can to do this with

1docker-compose down -v

But warning: This also delete volumes with all of its data In production, I recommend using host folders mapping instead of volumes which docker-compose down -v not deleting.

Updating

To update the container you can run

1docker-compose pull
2docker-compose up -d

Which will download newer docker images and recreate containers.

Done

And that’s your very own WordPress running in docker containers. If you have more questions you can contact me over email, my Mastodon or Matrix account.

#Docker #Wordpress