Easy Steps to Set Up Wordpress in Docker

·

6 min read

I'm not a huge fan of WordPress. In my honest opinion, the main problem (and benefit?) with WordPress is the abundance of plugins. It's like a double-edged sword; on one side, you have plugins for almost everything, and on the other side, you have the quality of those plugins to consider. Some plugins are well-supported by their creators to ensure compatibility with the latest version of WordPress. However, sometimes just one outdated plugin on your site can prevent you from upgrading to a new version because the developer is no longer updating it.

It's even worse if your impressive theme, which you purchased, relies on one of these plugins (in addition to another 20 required for your theme to function). Perhaps you, dear reader, are one of those creators who use many plugins in your theme. If so, I'd like to offer some advice: pay more attention to the plugins you use as dependencies in your theme.

Now, let's get back to configuring WordPress on Docker. Once we're finished, your server will be able to run everything below with just one command.

  • Deploy wordpress site

  • Deploy and configure databse

  • Generate SSL certificate.

What you will need? (Prerequisites)

  • Ovn server (or VPS) with installed docker & docker-compose.

  • Own domain (preferable hosted on Cloudflare)

  • Configured DNS server which point address to your server

If you want to host from home, you need to have a static external IP address and properly set up NAT on your router. Another, more advanced option is to create a VPS with a cloud provider and establish a VPN network. Then, you can forward requests to your server from the proxy to your home server over VPN. Now, back to Docker...

If you haven't 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:

version: "3.9"

networks:
  frontend:
    external: true
  wordpress_backend:
    internal: true

We will have two networks:

  • frontend: This will be used for communication between the internet and our application.

  • wordpress_backend: This network facilitates communication among WordPress, the database, and Nginx. It is not accessible from the internet.

External networks are not automatically created with Docker Compose, so you must create them manually:

docker network create frontend

Services

version: "3.9"

networks:
  frontend:
    external: true
  wordpress_backend:
    internal: true

services:
  # here goes services

Traefik

First, add the Traefik service. This serves as our reverse proxy, enabling connections to our app, upgrading HTTP to HTTPS, and requesting SSL certificates for services behind it.

wordpress_traefik:
  image: traefik
  command:
    --configFile=/traefik.yml
  restart: unless-stopped
  networks:
    - frontend
    - wordpress_backend
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - /etc/localtime:/etc/localtime:ro
    - /var/run/docker.sock:/var/run/docker.sock:ro
    - ./traefik.yml:/traefik.yml:ro
    - ./tls_config.yml:/tls_config.yml:ro
    - ./letsencrypt:/letsencrypt
  environment:
    - CF_API_EMAIL=<your-email>
    - CF_API_KEY=<your-cloudflare-api-key>

I am using the DNS challenge to renew and generate certificates, but there are more supported providers.

I use Cloudflare for most of my domains, so if you want to configure Traefik to use it as your DNS provider, add the following code to your traefik.yml file:

# traefik.yml
# ...
certificatesresolvers:
  le:
    acme:
      httpchallenge:
        entrypoint: "web"
      emaiL: "<change to your email>"
      storage: "/letsencrypt/acme.json"
      dnschallenge:
        provider: "cloudflare"
# ...

Another important configuration is to disable unsupported versions of TLS and SSL. By default, Traefik enables the use of all versions of SSL and TLS. This preset can be modified using the following code.

# tls_config.yml
tls:
  options:
    mytls:
      sniStrict: true
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
    mintls13:
      minVersion: VersionTLS13

With this config, you can get an "A+" rank for your server with SSL Labs test

Wordpress Web

I am using an FPM image for WordPress, and for the server, I'm utilizing Nginx. First and foremost, add another service configuration for your NGINX to your docker-compose.yml file as follows:

wordpress_proxy:
  image: nginx:alpine
  restart: unless-stopped
  depends_on:
    - wordpress_app
    - wordpress_db
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf:ro
  volumes_from:
    - wordpress_app
    - wordpress_traefik
  networks:
    - wordpress_backend

Next, I need to provide Traefik with more information about the services in my docker-compose file, such as where to find my server and how to connect to it. This can be achieved by adding labels to each service that needs to be accessible from the internet through the Traefik proxy. Here is an example of my NGINX service.

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

Nginx is used as an HTTP server for the PHP-FPM image, which contains my WordPress website. However, before using it, you must configure it. Create an nginx.conf file and add the following code to it.

# nginx.conf
user nginx;

events {
  worker_connections 768;
}

http {
  upstream backend {
    server wordpress_app:9000;
  }

  include /etc/nginx/mime.types;
  default_type application/octet-stream;
  gzip on;
  gzip_disable "msie6";

  server {
    listen 80;

    root /var/www/html/;
    index index.php index.html index.htm;

    location / {
      # try_files $uri $uri/ =404;
      try_files $uri $uri/ /index.php?$args;
    }

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
      root /usr/share/nginx/html;
    }

    location = /favicon.ico {
      log_not_found off;
      access_log off;
    }

    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)$ {
      access_log off; log_not_found off; expires max;
    }


    location ~ [^/]\.php(/|$) {
      fastcgi_split_path_info ^(.+?\.php)(/.*)$;
      if (!-f $document_root$fastcgi_script_name) {
        return 404;
      }
      # This is a robust solution for path info security issue and works with "cgi.fix_pathinfo = 1" in /etc/php.ini (default)

      include fastcgi_params;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param PHP_VALUE "upload_max_filesize=64m
      post_max_size=64m";
      fastcgi_pass wordpress_app:9000;
    }
  }
}

Wordpress

Add the WordPress service, utilizing the FPM version. WordPress requires internet access, so include it in both the frontend network for internet connectivity and the wordpress_backend network for database connection capabilities. Traefik is not necessary in this case, so disable it by adding the label "traefik.enable=false".

wordpress_app:
  image:  wordpress:5-php8.0-fpm-alpine # wordpress:5-fpm-alpine
  restart: unless-stopped
  depends_on:
    - wordpress_db
  networks:
    - wordpress_backend
    - frontend
  environment:
    WORDPRESS_DB_HOST: wordpress_db
    WORDPRESS_DB_USER: exampleuser
    WORDPRESS_DB_PASSWORD: examplepass
    WORDPRESS_DB_NAME: exampledb
  volumes:
    - wordpress:/var/www/html
  labels:
    - "traefik.enable=false"

Database

WordPress requires a MySQL database to function properly. You can also use MariaDB as an alternative. Ensure that the environment variables correspond with the WordPress DB variables. Upon the first run, WordPress will automatically create all necessary database tables.

wordpress_db:
  image: mysql:8.0.21
  restart: unless-stopped
  command: --default-authentication-plugin=mysql_native_password
  networks:
    - wordpress_backend
  environment:
    MYSQL_DATABASE: exampledb
    MYSQL_USER: exampleuser
    MYSQL_PASSWORD: examplepass
    MYSQL_RANDOM_ROOT_PASSWORD: '1'
  volumes:
    - db:/var/lib/mysql

Adminer

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

adminer:
  image: adminer
  depends_on:
    - wordpress_db
  networks:
    - frontend
    - wordpress_backend
  restart: always

Volumes

Volumes are used to store your data. If you prefer to map host folders rather than using volumes, you can omit the following lines of code. In your docker-compose file, the structure is as follows:

version: "3.9"

networks:
  # your networks

services:
  # your services

# Add following content at the end
volumes:
  wordpress:
    driver: local
  db:
    driver: local

Folder structure

Your folder has to contain the following files

docker-compose.yml
nginx.conf
tls_config.yml
traefik.yml

Start it

Ok let's start our Wordpress

docker-compose up -d

Navigate to your designated address. WordPress will prompt you to create a new admin user and password.

Delete it

If you need to delete it, you can do so with

docker-compose down -v

However, be cautious: This will also delete volumes along with all of their data. In a production environment, I recommend using host folder mapping or external volumes instead of (internal) volumes, the ones that are created with your application in compose file, as this data will not be deleted when using "docker-compose down -v."

Updating

To update the container, you can execute the following commands:

docker-compose pull
docker-compose up -d

The commands above will download newer docker images and recreate containers.