Below is a set of instructions that can be followed to setup a WordPress website using Docker on a freshly installed Ubuntu 20.04.3 LTS (Focal Fossa) server.
Initial server setup
ssh root@your_server_ip
adduser sammy
sudo deluser --remove-home ubuntu
usermod -aG sudo sammy
apt update
apt install ufw
ufw app list
ufw allow OpenSSH
ufw enable
ufw status
Some extra useful commands
less /etc/passwd #overview of the system users
cat /etc/os-release #check OS version
df -h / #disk usage
lscpu #cpu information
free -h #memory usage
export LC_ALL=C #change terminal language to default
unset LC_ALL
docker-compose config #helps with debugging
Installing Docker
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
apt-cache policy docker-ce
sudo apt-get update
sudo apt install docker-ce
sudo systemctl status docker
Installing Docker Compose
mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v2.4.1/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
sudo apt install docker-compose #this one did the trick for me
Setting up WordPress, MySQL, NGINX, and Certbot with Docker Compose
mkdir wordpress && cd wordpress
mkdir nginx-conf
nano nginx-conf/nginx.conf
nginx.conf
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
index index.php index.html index.htm;
root /var/www/html;
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
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;
}
location = /favicon.ico {
log_not_found off; access_log off;
}
location = /robots.txt {
log_not_found off; access_log off; allow all;
}
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}
nano .env #create .gitignore and .dockerignore then add .env if you intend to use git
.env
MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_USER=your_wordpress_database_user
MYSQL_PASSWORD=your_wordpress_database_password
nano docker-compose.yml
docker-compose.yml
version: '3'
services:
db:
image: mysql:8.0
container_name: db
restart: unless-stopped
env_file: .env
environment:
- MYSQL_DATABASE=wordpress
volumes:
- dbdata:/var/lib/mysql
command: '--default-authentication-plugin=mysql_native_password'
networks:
- app-network
wordpress:
depends_on:
- db
image: wordpress:5.1.1-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
volumes:
- wordpress:/var/www/html
networks:
- app-network
webserver:
depends_on:
- wordpress
image: nginx:1.15.12-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 example.com -d www.example.com
volumes:
certbot-etc:
wordpress:
dbdata:
networks:
app-network:
driver: bridge
docker-compose up -d
docker-compose ps
#for logs run $docker-compose logs service_name
docker-compose exec webserver ls -la /etc/letsencrypt/live
nano docker-compose.yml
#replace --staging with --force-renewal
docker-compose up --force-recreate --no-deps certbot
docker-compose stop webserver
curl -sSLo nginx-conf/options-ssl-nginx.conf https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf
rm nginx-conf/nginx.conf
nano nginx-conf/nginx.conf
nginx.conf
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
location / {
rewrite ^ https://$host$request_uri? permanent;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
index index.php index.html index.htm;
root /var/www/html;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/nginx/conf.d/options-ssl-nginx.conf;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# enable strict transport security only if you understand the implications
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;
}
location = /favicon.ico {
log_not_found off; access_log off;
}
location = /robots.txt {
log_not_found off; access_log off; allow all;
}
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}
nano docker-compose.yml
#add - "443:443"
docker-compose up -d --force-recreate --no-deps webserver
docker-compose ps
Then go to example.com and follow the WordPress setup wizard
Renewing Certificates
nano ssl_renew.sh
which docker #to know its path for bash script
which docker-compose #to know its path for bash script
ssl_renew.sh
#!/bin/bash
COMPOSE="/usr/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"
cd /home/sammy/wordpress/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af
chmod +x ssl_renew.sh
sudo crontab -e
*/5 * * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1 #runs every five minutes for testing
tail -f /var/log/cron.log #check if the test is successful
0 0 * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1 #every midnight
nano ssl_renew.sh #remove –dry-run from
Sources
- Initial Server Setup with Ubuntu 18.04 | DigitalOcean
- How To Install Nginx on Ubuntu 20.04 | DigitalOcean
- How To Install and Use Docker on Ubuntu 20.04 | DigitalOcean
- How To Install and Use Docker Compose on Ubuntu 20.04 | DigitalOcean
- How To Install WordPress With Docker Compose | DigitalOcean
- How to Do a Clean Restart of a Docker Instance (tibco.com)
- Crontab.guru – The cron schedule expression editor