In 2025, WordPress is still one of the most popular open-source content management systems (CMS) in the world. Blogs, company websites, e-commerce stores, and landing pages all rely heavily on it.
Marcus Johnson
Site Reliability Engineer (SRE)
Nov 27, 2025
7 min read

Traditionally, WordPress is deployed by installing a full LAMP/LEMP stack (Linux + web server + MySQL + PHP), which can be tedious to set up and maintain over time.
With Docker + Docker Compose, we can break the stack into separate containers—web server, PHP-FPM, MySQL, certificate management—and orchestrate them with a single docker-compose.yml file. This greatly simplifies the deployment process and makes the environment more standardized, portable, and easy to reproduce.
If you combine this with a high-performance VPS, such as a BrainHost.ai KVM VPS (NVMe storage + ~30s provisioning), you can go from “empty server” to “live HTTPS WordPress site” in just a few minutes.
In this guide, you’ll:
To follow along, you’ll need the following (Ubuntu 22.04+ is recommended):
You can use any cloud provider, but here we’ll recommend a BrainHost.ai KVM VPS, because:
Once you’ve picked a plan, SSH into your VPS and create a normal user with sudo privileges.
On the server, install Docker and Docker Compose:
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sh
# Install Docker Compose (v2.x plugin)
sudo apt install docker-compose-plugin -y
Assume your domain is your_domain. Create the following DNS records:
your_domain → A record pointing to your server’s public IPwww.your_domain → A record pointing to your server’s public IPIf you’re using a BrainHost VPS, simply point your A records to the IP assigned by BrainHost.
Open SSH, HTTP, and HTTPS in your firewall:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

We’re going to build the following setup:
All four containers communicate on a custom Docker bridge network (e.g., app-network), and only ports 80 and 443 are exposed to the outside world. Data is persisted using Docker named volumes, so even if containers are recreated, your data is preserved.
mkdir ~/wordpress
cd ~/wordpress
mkdir nginx-conf
Create the config file:
nano nginx-conf/nginx.conf
Add a basic HTTP (port 80) config that we’ll use later to obtain certificates:
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
index index.php index.html index.htm;
root /var/www/html;
# Let’s Encrypt HTTP-01 challenge directory
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
# Route all requests through WordPress front controller
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# PHP-FPM handling
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
# Deny .ht* files
location ~ /\.ht {
deny all;
}
# Small optimization for static files
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; allow all; }
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}
Save and exit.
Note:
fastcgi_pass wordpress:9000useswordpressas the hostname—that’s the service name we’ll define for the WordPress container in Docker Compose.
.env to Store Sensitive Environment VariablesTo avoid hardcoding database passwords in docker-compose.yml, we’ll store them in a .env file and reference that in our Compose config.
.envnano .env
Example:
# MySQL
MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_USER=your_wp_user
MYSQL_PASSWORD=your_wp_password
# General settings
WORDPRESS_DB_NAME=wordpress
.env in GitIf you use Git for version control, create a .gitignore:
nano .gitignore
Add:
.env
.env in Docker buildsSimilarly, create .dockerignore:
nano .dockerignore
Example content:
.env
.git
.dockerignore
docker-compose.yml
This helps keep sensitive info and unnecessary files out of your images.
docker-compose.yml (Four Services)Next, we’ll define all four services—database, WordPress, Nginx, and Certbot—in one Compose file.
nano docker-compose.yml
Example configuration:
version: '3'
services:
db:
image: mysql:8.0
container_name: db
restart: unless-stopped
env_file: .env
environment:
- MYSQL_DATABASE=${WORDPRESS_DB_NAME:-wordpress}
volumes:
- dbdata:/var/lib/mysql
command: '--default-authentication-plugin=mysql_native_password'
networks:
- app-network
wordpress:
depends_on:
- db
image: wordpress:php8.2-fpm-alpine
container_name: wordpress
restart: unless-stopped
env_file: .env
environment:
- WORDPRESS_DB_HOST=db:3306
- WORDPRESS_DB_USER=${MYSQL_USER}
- WORDPRESS_DB_PASSWORD=${MYSQL_PASSWORD}
- WORDPRESS_DB_NAME=${WORDPRESS_DB_NAME:-wordpress}
volumes:
- wordpress:/var/www/html
networks:
- app-network
webserver:
depends_on:
- wordpress
image: nginx:1.25-alpine
container_name: webserver
restart: unless-stopped
ports:
- "80:80"
volumes:
- wordpress:/var/www/html
- ./nginx-conf:/etc/nginx/conf.d
- certbot-etc:/etc/letsencrypt
networks:
- app-network
certbot:
depends_on:
- webserver
image: certbot/certbot
container_name: certbot
volumes:
- certbot-etc:/etc/letsencrypt
- wordpress:/var/www/html
command: >
certonly --webroot
--webroot-path=/var/www/html
--email [email protected]
--agree-tos
--no-eff-email
--staging
-d your_domain
-d www.your_domain
volumes:
certbot-etc:
wordpress:
dbdata:
networks:
app-network:
driver: bridge
Quick notes:
db servicemysql:8.0 and stores data in the dbdata volume--default-authentication-plugin=mysql_native_password ensures compatibility with WordPress’s MySQL clientwordpress servicewordpress:php-fpm-alpine variant with PHP-FPM built in.envwebserver servicecertbot servicewebroot mode for HTTP-01 challenge--staging to test without hitting rate limits
docker compose up -d
(If you’re on an older system, docker-compose up -d also works.)
docker compose ps
You should see:
db, wordpress, and webserver as Upcertbot as Exit 0, which means it completed successfullydocker compose exec webserver ls -la /etc/letsencrypt/live
If you see a directory named after your domain (e.g., your_domain), the test certificate was generated successfully.
Edit docker-compose.yml and replace --staging with --force-renewal in the certbot command. Then run:
docker compose up --force-recreate --no-deps certbot
This time, Certbot will request a real production certificate.
Now that the certificates live under /etc/letsencrypt/live/your_domain, we can update the Nginx config to serve HTTPS.
Edit nginx-conf/nginx.conf and add an HTTPS server block alongside (or in addition to) the existing HTTP one:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your_domain www.your_domain;
root /var/www/html;
index index.php index.html index.htm;
ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;
# Optional: add extra security headers or TLS settings here
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.ht {
deny all;
}
}
You can then convert the original HTTP server on port 80 into a simple redirect to HTTPS:
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
return 301 https://$host$request_uri;
}
Restart Nginx to apply the changes:
docker compose restart webserver
Open your browser and navigate to:
https://your_domain
You should see the WordPress installation wizard:
At this point, your Docker Compose-based WordPress site is fully up and running with HTTPS enabled.
Let’s Encrypt certificates are usually valid for 90 days. We need a scheduled job to run certbot renew periodically and reload Nginx when certificates are renewed.
On the host machine:
sudo crontab -e
Add a cron entry (e.g., run every day at 3 AM):
0 3 * * * docker compose run --rm certbot renew && docker compose exec webserver nginx -s reload
This way, your certificates will stay valid without manual intervention.
Throughout this guide, server performance and network quality are critical to your site’s responsiveness, admin experience, and backup/upgrade workflow. Here’s why this stack fits very well on BrainHost.ai:
If you’re planning to build a WordPress network, content farm, or blog cluster, Docker Compose + BrainHost VPS is a highly efficient and repeatable combination. Just treat this project directory as a template, change the domain and
.envvalues, and you can spin up a new stack on a new server in minutes.
In this tutorial, you’ve learned how to:
.env fileFrom here, you can:
~/wordpress-site1, ~/wordpress-site2, etc., and host multiple WordPress sites on the same BrainHost VPSdocker-compose.yml and related config into a Git repo and turn this into your standard, reusable deployment templateOnce you have a BrainHost VPS and you’ve completed the steps in this guide, your WordPress production environment becomes replicable, portable, and easy to maintain—upgrading PHP, migrating data, or scaling to multiple sites will be much simpler going forward.
Tags
Marcus Johnson
Site Reliability Engineer (SRE)
Site Reliability Engineer with expertise in monitoring and incident response.
Related Articles

Effortless Deployment: Install Docker and Run Hello World on Your BrainHost VPS
A step-by-step tutorial guiding you to install the Docker Engine on your BrainHost VPS (Ubuntu/Debian systems) and run your first container (Hello World), ensuring consistent, rapid deployment.

Practical VPS Guide for Engineers: From Hobby to Production
A comprehensive guide for engineers covering VPS fundamentals, virtualization stack, practical use cases, best practices for networking, storage, security, and building a production-ready system.

How to Choose the Best CMS
A comprehensive guide to Content Management Systems (CMS) for blogging and publishing, covering the key differences between Traditional and Headless models, and recommending top platforms like WordPress, Contentful, and HubSpot based on use case, scale, and team needs.
No Previous Article
BrainHost - A reliable VPS hosting platform offering high-performance virtual servers with advanced management capabilities.