Dockerizing WordPress with MariaDB and Nginx as Proxy: A Complete Guide

In the modern era of web development, containerization has become a game changer, allowing developers to package applications and their dependencies into isolated environments for consistent and replicable deployments. In this guide, we will explore the process of dockerizing a WordPress application using MariaDB as the database and Nginx as a reverse proxy. This setup will ensure high performance, security, and ease of maintenance.

Architecture Overview

The architecture consists of three primary services:

  1. MariaDB: A robust database management system that will store all WordPress data.
  2. WordPress: The content management system that powers the web application.
  3. Nginx: A lightweight and powerful web server that acts as a reverse proxy, managing HTTP requests and serving static files efficiently.

Prerequisites

Before diving into the deployment process, ensure you have the following installed on your local development machine:

  • Docker
  • Docker Compose

Project Structure

Create a project directory with the following structure:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
your-project/
├── docker-compose.yml
└── nginx/
├── database/
├── html/
├── nginx-conf/
└── ssl/ # For SSL certificates
your-project/ ├── docker-compose.yml └── nginx/ ├── database/ ├── html/ ├── nginx-conf/ └── ssl/ # For SSL certificates
your-project/
├── docker-compose.yml
└── nginx/
    ├── database/
    ├── html/
    ├── nginx-conf/
    └── ssl/  # For SSL certificates

Step 1: Create the Docker Compose File

Inside your project directory, create a

docker-compose.yml
docker-compose.yml file with the following content to define our services:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
version: '3'
services:
mysql:
image: mariadb
volumes:
- ./nginx/database/mysql:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: SECURE_PASSWORD
MYSQL_DATABASE: wordpress
MYSQL_USER: USERNAME
MYSQL_PASSWORD: SECURE_PASSWORD
restart: unless-stopped
networks:
- docker_network
wordpress:
image: wordpress:6.2-php8.0-fpm-alpine
volumes:
- ./nginx/html/:/usr/share/nginx/html
depends_on:
- mysql
environment:
WORDPRESS_DB_HOST: mysql
MYSQL_ROOT_PASSWORD: SECURE_PASSWORD
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: USERNAME
WORDPRESS_DB_PASSWORD: SECURE_PASSWORD
WORDPRESS_TABLE_PREFIX: wp_
networks:
- docker_network
restart: unless-stopped
nginx:
image: nginx:alpine
volumes:
- ./nginx/nginx-conf:/etc/nginx/conf.d
- ./nginx/html:/usr/share/nginx/html
- ./nginx/ssl:/etc/ssl
ports:
- 80:80
- 443:443
networks:
- docker_network
restart: unless-stopped
networks:
docker_network:
driver: bridge
version: '3' services: mysql: image: mariadb volumes: - ./nginx/database/mysql:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: SECURE_PASSWORD MYSQL_DATABASE: wordpress MYSQL_USER: USERNAME MYSQL_PASSWORD: SECURE_PASSWORD restart: unless-stopped networks: - docker_network wordpress: image: wordpress:6.2-php8.0-fpm-alpine volumes: - ./nginx/html/:/usr/share/nginx/html depends_on: - mysql environment: WORDPRESS_DB_HOST: mysql MYSQL_ROOT_PASSWORD: SECURE_PASSWORD WORDPRESS_DB_NAME: wordpress WORDPRESS_DB_USER: USERNAME WORDPRESS_DB_PASSWORD: SECURE_PASSWORD WORDPRESS_TABLE_PREFIX: wp_ networks: - docker_network restart: unless-stopped nginx: image: nginx:alpine volumes: - ./nginx/nginx-conf:/etc/nginx/conf.d - ./nginx/html:/usr/share/nginx/html - ./nginx/ssl:/etc/ssl ports: - 80:80 - 443:443 networks: - docker_network restart: unless-stopped networks: docker_network: driver: bridge
version: '3'
services:
  mysql:
    image: mariadb
    volumes:
      - ./nginx/database/mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: SECURE_PASSWORD
      MYSQL_DATABASE: wordpress
      MYSQL_USER: USERNAME
      MYSQL_PASSWORD: SECURE_PASSWORD
    restart: unless-stopped
    networks:
      - docker_network

  wordpress:
    image: wordpress:6.2-php8.0-fpm-alpine
    volumes:
      - ./nginx/html/:/usr/share/nginx/html
    depends_on:
      - mysql
    environment:
      WORDPRESS_DB_HOST: mysql
      MYSQL_ROOT_PASSWORD: SECURE_PASSWORD
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: USERNAME
      WORDPRESS_DB_PASSWORD: SECURE_PASSWORD
      WORDPRESS_TABLE_PREFIX: wp_
    networks:
      - docker_network
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx/nginx-conf:/etc/nginx/conf.d
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/ssl:/etc/ssl
    ports:
      - 80:80
      - 443:443
    networks:
      - docker_network
    restart: unless-stopped

networks:
  docker_network:
    driver: bridge

Explanation of the Docker Compose File

  • The mysql service uses the official MariaDB image and sets up the database with necessary credentials.
  • The wordpress service uses the official WordPress image and connects to the MariaDB instance.
  • The nginx service uses a lightweight Nginx image, exposing ports 80 and 443 for web traffic.

Step 2: Download WordPress

Navigate to your

nginx/html
nginx/html directory and download the latest version of WordPress:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Move to the HTML directory
cd your-project/nginx/html
# Download the latest version of WordPress
curl -O https://wordpress.org/latest.tar.gz
# Extract the WordPress files
tar -xvzf latest.tar.gz --strip-components=1
# Clean up the tar file
rm latest.tar.gz
# Move to the HTML directory cd your-project/nginx/html # Download the latest version of WordPress curl -O https://wordpress.org/latest.tar.gz # Extract the WordPress files tar -xvzf latest.tar.gz --strip-components=1 # Clean up the tar file rm latest.tar.gz
# Move to the HTML directory
cd your-project/nginx/html

# Download the latest version of WordPress
curl -O https://wordpress.org/latest.tar.gz

# Extract the WordPress files
tar -xvzf latest.tar.gz --strip-components=1

# Clean up the tar file
rm latest.tar.gz

Step 3: Create Nginx Configuration

Inside the nginx/nginx-conf directory, create a file named

default.conf
default.conf for your Nginx configuration:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir -p your-project/nginx/nginx-conf
nano your-project/nginx/nginx-conf/default.conf
mkdir -p your-project/nginx/nginx-conf nano your-project/nginx/nginx-conf/default.conf
mkdir -p your-project/nginx/nginx-conf
nano your-project/nginx/nginx-conf/default.conf

Copy the following generalized configuration for your domain:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# HTTP Configuration
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com; # Change this to your domain
location ~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html; # Point to the web root directory where .well-known is stored
}
location / {
rewrite ^ https://$host$request_uri? permanent; # Redirect all HTTP traffic to HTTPS
}
}
# HTTPS Configuration
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com www.yourdomain.com; # Change this to your domain
index index.php index.html index.htm;
root /usr/share/nginx/html; # Point to the web root directory where WordPress is installed
server_tokens off;
client_max_body_size 100M; # Adjust as needed for uploads
ssl_certificate /usr/share/nginx/ssl/yourdomain.com.fullchain.pem; # Update with your certificate file
ssl_certificate_key /usr/share/nginx/ssl/yourdomain.com.privkey.pem; # Update with your private key
# Security Headers
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 'self' data: 'unsafe-eval' 'unsafe-inline';" always;
# Handle WordPress URL Rewriting
location / {
try_files $uri $uri/ /index.php$is_args$args; # Use WordPress's pretty permalinks
}
# Handle PHP Files
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress:9000; # Ensure this points to your PHP-FPM service
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
# Deny access to .htaccess and .user.ini files
location ~ /\.ht {
deny all;
}
location ~ ^/\.user\.ini {
deny all;
}
# Favicon and robots.txt settings
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
log_not_found off;
access_log off;
allow all;
}
# Cache static content
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}
# HTTP Configuration server { listen 80; listen [::]:80; server_name yourdomain.com www.yourdomain.com; # Change this to your domain location ~ /.well-known/acme-challenge { allow all; root /usr/share/nginx/html; # Point to the web root directory where .well-known is stored } location / { rewrite ^ https://$host$request_uri? permanent; # Redirect all HTTP traffic to HTTPS } } # HTTPS Configuration server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name yourdomain.com www.yourdomain.com; # Change this to your domain index index.php index.html index.htm; root /usr/share/nginx/html; # Point to the web root directory where WordPress is installed server_tokens off; client_max_body_size 100M; # Adjust as needed for uploads ssl_certificate /usr/share/nginx/ssl/yourdomain.com.fullchain.pem; # Update with your certificate file ssl_certificate_key /usr/share/nginx/ssl/yourdomain.com.privkey.pem; # Update with your private key # Security Headers 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 'self' data: 'unsafe-eval' 'unsafe-inline';" always; # Handle WordPress URL Rewriting location / { try_files $uri $uri/ /index.php$is_args$args; # Use WordPress's pretty permalinks } # Handle PHP Files location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass wordpress:9000; # Ensure this points to your PHP-FPM service fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } # Deny access to .htaccess and .user.ini files location ~ /\.ht { deny all; } location ~ ^/\.user\.ini { deny all; } # Favicon and robots.txt settings location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } # Cache static content location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; } }
# HTTP Configuration
server {
    listen 80;
    listen [::]:80;

    server_name yourdomain.com www.yourdomain.com;  # Change this to your domain

    location ~ /.well-known/acme-challenge {
        allow all;
        root /usr/share/nginx/html;  # Point to the web root directory where .well-known is stored
    }

    location / {
        rewrite ^ https://$host$request_uri? permanent;  # Redirect all HTTP traffic to HTTPS
    }
}

# HTTPS Configuration
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;  # Change this to your domain

    index index.php index.html index.htm;

    root /usr/share/nginx/html;  # Point to the web root directory where WordPress is installed

    server_tokens off;

    client_max_body_size 100M;  # Adjust as needed for uploads

    ssl_certificate /usr/share/nginx/ssl/yourdomain.com.fullchain.pem;  # Update with your certificate file
    ssl_certificate_key /usr/share/nginx/ssl/yourdomain.com.privkey.pem;  # Update with your private key

    # Security Headers
    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 'self' data: 'unsafe-eval' 'unsafe-inline';" always;

    # Handle WordPress URL Rewriting
    location / {
        try_files $uri $uri/ /index.php$is_args$args;  # Use WordPress's pretty permalinks
    }

    # Handle PHP Files
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass wordpress:9000;  # Ensure this points to your PHP-FPM service
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    # Deny access to .htaccess and .user.ini files
    location ~ /\.ht {
        deny all;
    }

    location ~ ^/\.user\.ini {
        deny all;
    }

    # Favicon and robots.txt settings
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        log_not_found off;
        access_log off;
        allow all;
    }

    # Cache static content
    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        log_not_found off;
    }
}

Important Adjustments

  • Domain Name: Replace
    yourdomain.com
    yourdomain.com with your actual domain in the
    server_name
    server_name directives.
  • SSL Certificates: Ensure to provide the correct paths to your SSL certificate and key files.

Step 4: Running Your Application

Once you have set everything up, you can run the application with Docker:

  1. Navigate back to your project directory.
  2. Execute the following command to start the services:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
docker-compose up -d
docker-compose up -d
docker-compose up -d
  1. Visit
    http://localhost
    http://localhost or your domain in a web browser.

Step 5: Complete WordPress Installation

Upon visiting your site, you will encounter the WordPress installation wizard. Follow the instructions to set up your site title, username, password, and email.

Conclusion

By dockersing WordPress with MariaDB and Nginx, you achieve a powerful, scalable, and maintainable web environment. This setup simplifies the deployment process and allows you to easily manage updates and security configurations.

With your Dockerized WordPress site now live, feel free to customize it, install themes, and add plugins to suit your needs. Enjoy creating with WordPress in a containerized environment!

Troubleshooting: Migrating Your Docker Instance / Permission Errors

When migrating your Dockerized WordPress application from one computer to another, you may encounter file permission issues, especially if you are using a volume to persist your WordPress data. Here’s how to resolve these issues and ensure your application operates smoothly after the migration.

Step 1: Identify User IDs

Before you migrate your Docker containers, it’s essential to determine the user IDs that Nginx and WordPress run as. This ensures that the file permissions are set correctly in the new environment so that Nginx can serve WordPress files without permission issues.

  1. Access the WordPress Container: To find the user ID for both Nginx and WordPress, you will need to get into the WordPress container:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
docker exec -it <wordpress_container_name> /bin/sh
docker exec -it <wordpress_container_name> /bin/sh
docker exec -it <wordpress_container_name> /bin/sh

Once inside, Check the running processes to see which user is running the app e.g. webserver:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ps aux
ps aux
ps aux

Check the user ID:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
id www-data
id www-data
id www-data
  1. Repeat for Nginx: Similarly, access the Nginx container:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
docker exec -it <nginx_container_name> /bin/sh
docker exec -it <nginx_container_name> /bin/sh
docker exec -it <nginx_container_name> /bin/sh

Again, check the user ID for

www-data
www-data:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
id www-data
id www-data
id www-data

Step 2: Change Ownership and Set Permissions

After identifying the appropriate user IDs, ensure that the ownership and permissions on your WordPress files are correct. This will help prevent issues with file uploads and other file operations.

  1. Set Ownership: While still inside the WordPress container, update the ownership of the html directory:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
chown -R www-data:www-data /usr/share/nginx/html
chown -R www-data:www-data /usr/share/nginx/html
chown -R www-data:www-data /usr/share/nginx/html
  1. Set Directory Permissions: Set the correct permissions for directories and files within the html directory:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
find /usr/share/nginx/html -type d -exec chmod 755 {} \;
find /usr/share/nginx/html -type f -exec chmod 644 {} \;
find /usr/share/nginx/html -type d -exec chmod 755 {} \; find /usr/share/nginx/html -type f -exec chmod 644 {} \;
find /usr/share/nginx/html -type d -exec chmod 755 {} \;
find /usr/share/nginx/html -type f -exec chmod 644 {} \;

Step 3: Restart Containers

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
docker-compose down
docker-compose up -d
docker-compose down docker-compose up -d
docker-compose down
docker-compose up -d

Step 4: Verify Your Setup

Check that your WordPress site functions correctly after migration:

  • Navigate to your website and ensure that pages load.
  • Test uploading files through the WordPress admin interface to confirm that permissions are correctly set.
0 0 votes
Article Rating
Subscribe
Notify of
guest


0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x