Easy Steps to Set Up Wordpress in Docker
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.