I’ve been running my personal blog with DigitalOcean’s ubuntu droplet (cloud server) for many years. Recently I needed to setup another. Since my existing site didn’t take up even 1/3 of the resource provided by a 1GB ubuntu droplet, I figured it’s worth trying to run both sites within a single droplet, with the help of Docker.
I chose Docker to help me segregate the two sites. If there’s a need in the future, I could easily split the two sites, or even run Kubernetes clusters for them.
The fundamental basic setup comes from DigitalOcean’s own guide: How To Install WordPress With Docker Compose. I’ll list down what I’ve done differently below:
Step 1 — Defining the Web Server Configuration:
Instead of one nginx config file (nginx-conf/nginx.conf), I had two, i.e. (nginx-conf/site1.conf and nginx-conf/site2.conf).
I chose /srv/site1
and /srv/site2
as document root in the respective configuration file, because, firstly, they can’t both share the same mount path of /var/www/html
, and secondly, I’m more used to having the sites under /srv
.
Step 2 — Defining Environment Variables:
Similarly, I had two sets of mysql user and password for each site
Step 3 — Defining Services with Docker Compose:
Basically for each site, I have a mysql service, a wordpress service, then for all of them, it’s sharing one single nginx service.
In docker-compose.yml, the mount point for the wordpress service’s volumes needs to point to /var/www/html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
wordpress_fb: depends_on: - db_fb image: wordpress:5-php7.4-fpm-alpine restart: unless-stopped environment: - WORDPRESS_DB_HOST=db_fb:3306 - WORDPRESS_DB_USER=${FB_MYSQL_USER} - WORDPRESS_DB_PASSWORD=${FB_MYSQL_PASSWORD} - WORDPRESS_DB_NAME=wordpress volumes: - wordpress_fb:/var/www/html networks: - app-network |
The nginx config files from the first step has to modify the location ~ \.php$
block in each file to:
1 2 3 4 5 6 7 8 9 |
location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass <strong>wordpress_fb</strong>:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME <strong>/var/www/html</strong>$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } |
where wordpress_fb is the respective docker service name for the wordpress, and /var/www/html is the mount point of its volume.
Back in docker-compose.yml, the nginx block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
nginx: depends_on: - wordpress_fb - wordpress_yy image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - wordpress_fb:/srv/site1 - wordpress_yy:/srv/site2 - ./etc_nginx/conf.d:/etc/nginx/conf.d - certbot_etc:/etc/letsencrypt - certbot_var:/var/lib/letsencrypt # not necessary but guard against docker volume prune networks: - app-network |
One significant difference against the DigitalOcean’s tutorial at this step is, I skipped over the certbot service. In fact, I started off with duplicating two set of cerbot services, but only to find out that certbot is meant to be run once every X weeks, and running two certbot services sharing the same certbot_etc doesn’t work, as its runtime uses file lock guarding against running multiple instances at the same time. I’ll share more about what to do with certs in the next step.
Step 4 — Obtaining SSL Certificates and Credentials:
So instead of defining the certbot as a service in docker-compose.yml, I run this script once:
1 2 3 |
docker run -it --rm --name certbot -v "docker-server_certbot_etc:/etc/letsencrypt" -v "docker-server_certbot_var:/var/lib/letsencrypt" -v "docker-server_wordpress_yy:/srv/yuan3y" certbot/certbot certonly --webroot --webroot-path=/srv/yuan3y --email myemail@example.com --agree-tos --no-eff-email --staging -d mydomain1.com -d www.mydomain1.com |
And run for the other domain as well, using the corresponding volume and webroot path.
Check if you got your both sites working. You can follow the rest of the tutorial. The certbot renew script, as I don’t intend to define the certbot service in docker-compose, the key line in the script is as below:
1 2 |
docker run -a stdout --rm --name certbot -v "your-docker-project-folder-name_certbot_etc:/etc/letsencrypt" -v "your-docker-project-folder-name_certbot_var:/var/lib/letsencrypt" -v "your-docker-project-folder-name_wordpress_fb:/srv/site1" -v "your-docker-project-folder-name_wordpress_yy:/srv/site2" -v "/var/log/letsencrypt/:/var/log/letsencrypt/" certbot/certbot renew |
Note that the volume names are prepended with your docker project folder’s name, as you can check using docker volume ls
command.
Once the certs are renewed, you should restart the nginx service to reload the certs:
1 2 |
docker-compose restart nginx |
Some other notes I’ve taken in this process of migration:
- If you’re going to change your IP address of old site to new site, prior to change, lower down the DNS TTL for A and AAAA records to 300 (seconds). This minimizes the amount of time where site visits to old and new server are non-deterministic.
- I had all the services running perfectly (without https) on my localhost with docker-compose up, but then once I tried to spin it up on the new droplet, the databases went into crashloop:
Starting XA crash recovery…
XA crash recovery finished.
And confusingly it worked well if I only start one site (nginx + 1 wordpress + 1 mysql). I stumbled upon it for quite a while before realizing the issue: it ran out of memory, because I had no swap space defined. - For the new server, if you’re starting a new droplet on DigitalOcean, do remember to add swap space.
- Memory comparison between old and new sites:
The old server running nginx and php-fpm serving 1 single wordpress site:MiB Mem : 994.0 total, 252.4 free, 366.4 used, 375.2 buff/cache
MiB Swap: 4096.0 total, 3703.5 free, 392.5 used. 434.1 avail Mem
The new server running docker spinning up nginx and 2 wordpress sites:MiB Mem : 981.3 total, 73.2 free, 567.2 used, 340.9 buff/cache
MiB Swap: 2048.0 total, 1378.4 free, 669.6 used. 176.8 avail Mem
That’s all for running multiple WordPress site using docker on a single server. I hope this helps you avoiding the mistakes that I made in this simple process.
If you’d like to run your own sites, deploy your next app in seconds, do try out DigitalOcean: https://m.do.co/t/4a809211c3ae. You get $100 in cloud credits from DigitalOcean using my link, which is sufficient for more than one and a half year’s worth for running the droplet with 1GB RAM (more than sufficient running 2 sites with docker as I’m using right now).