Skip to main content
  1. Posts/

🐋 How to Run Wordpress in Docker

·1239 words·6 mins·
docker wordpress
May Meow
Author
May Meow
MayMeow is a developer and cybersecurity enthusiast with a passion for cryptography, DevSecOps, and open-source contributions. They enjoy creating tools that strengthen digital security, blending creativity and technology to innovate in fields like PHP and .NET. Always exploring new frontiers in tech, MayMeow is dedicated to safeguarding the digital landscape through their work.
Table of Contents
selfhosted - This article is part of a series.
Part 4: This Article

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.

  • 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 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:

version: "3.9"

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

We will have two networks:

  • frontend: this will be used to comunicate from internet with our application
  • wordpress_backend: network for communicate between wprdpress, db and nginx… It is not awailable from internet.

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

docker network create frontend

Services
#

version: "3.9"

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

services:
  # 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.

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’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

# 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 has enabling to use each version of SSL and TLS. This preset can be changed by 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 “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

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 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.

# ... 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 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.

# 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 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.

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 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.

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 is mostly for development when you need to look inside the database.

adminer:
  image: adminer
  depends_on:
    - wordpress_db
  networks:
    - frontend
    - wordpress_backend
  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.

version: "3.9"

networks:
  # your networks

services:
  # your services

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

Folder sturcture
#

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

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

docker-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

docker-compose pull
docker-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.

Reply by Email
selfhosted - This article is part of a series.
Part 4: This Article

Related

Static Sites With Minio and S3www
·1297 words·7 mins
docker object storage static site selfhosting
Deploy Your Site to Azure with GitLab
·714 words·4 mins
tutorials azure gitlab hugo
How to Install and Configure Samba
·432 words·3 mins
Administration notes guide wiki file sharing linux windows