How To Install WordPress With Docker Compose

Introduction

WordPress is the most popular and widely used system of content management system (CMS). It is an instrument that ensures easy set up of websites and flexible blogs on MariaDB backend with PHP processing. This system is so popular because it helps to get your website up and running fast. With the system up, the other part of all administration procedures can be done through the web frontend.

A LEMP (Linux, Nginx, MySQL, and PHP) or LAMP (Linux, Apache, MySQL, and PHP) stacks are required to run WordPress. Installing these stacks is very complex and time-consuming; however, Docker and Docker Compose are the tools, which can greatly simplify the process of installing WordPress and setting up the preferred stack. They standardize configuration files, environment variables, and libraries, and run these images in containers. Moreover, Docker Compose helps to coordinate multiple containers — for instance, database and an application — to communicate with one another.

Using this tutorial, you will complete the process of a multi-container WordPress installation, with containers such as a WordPress, Nginx web server, and MySQL database. Your installation will be secured by TLS/SSL certificates with Let’s Encrypt. In the end, a cron job will be set up to renew your certificates to secure your domain in the long run.

Step One — Defining the Web Server Configuration


Before running any containers, the very first step  is to define the configuration for your Nginx web server. The blocks to be included into the configuration are the following: location blocks for WordPress, and a location block used to direct the verification requests of Let’s Encrypt  to the Certbot client used to renew automated certificates.

The first step is to create a wordpress project directory for your WordPress setup. Then navigate to it:

$ mkdir wordpress && cd wordpress

After that, create a directory for the configuration file:

$ mkdir nginx-conf

Use nano or any other editor to open the file:

$ nano nginx-conf/nginx.conf

The following blocks will be added to the file: location blocks to direct the request for certificates of Certbot client’s, as well as for static asset requests and PHP processing, and a server block containing directives for the document root and server name.

Insert the code provided below into the file. Make sure to type your own domain name to replace example.com:

~/wordpress/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;
        }
}

The following information is included in the server block:

Directives:

  • listen: the directive tells Nginx to listen on port 80. This will let us use the plugin Certbot webroot for the certificate requests. As you can see,that  port 443 is not included yet. The configuration to include SSL will be updated as soon as we successfully obtain our certificates.
  • server_name: the directive defines your server name, as well as the server block to be used for your server.requests. Do not forget to replace example.com in this line with your own domain name.
  • index: It defines the files to be used as indexes when requests to your server willbe processed. The default order of priority is modified by moving index.php in front of index.html .Itmeans that Nginx will prioritize files named index.php.
  • root: It names the root directory for requests to our server. It is named /var/www/html. It is a mount point at build time in our WordPress Dockerfile. These instructions of Dockerfile serve to ensure that the WordPress release files are mounted to this volume.

Location Blocks:

  •  The location block called location ~ /.well-known/acme-challenge will handle requests to the .well-known directory. In this directory, Certbot will place a temporary file to check that our domain DNS resolves to our server. Due to this configuration, we can use Certbot’s webroot plugin to obtain certificates for our domain.
  •  location /: Here, a try_files directive will be used to look for files matching individual URI requests. Control will be passed to WordPress’s index.php file with the request arguments instead of returning a 404 Not Found status .
  • location ~ \.php$ will handle PHP processing. It will also proxy these requests to our wordpress container. Since the php:fpm image is the basis for our WordPress Docker image, the configuration options specific to the FastCGI protocol will be also included in this block. PHP requests, Nginx needs an independent PHP processor. In our example, the php-fpm processor will handle those requests. This processor comes included with the php:fpm image. Also, FastCGI-specific variables, directives, and options  (requested by proxy to the WordPress application running in our wordpress container) set the index that is preferred for the parse URI requests and parsed request URI.
  • location ~ /\.ht: Nginx does not serve .htaccess files, thus this block will handle them . The directive deny_all directive is here to ensure that .htaccess files will not be served to users.
  • The blocks location = /favicon.ico, location = /robots.txt: These blocks ensure that requests to /favicon.ico and /robots.txt will not be logged.
  • location ~* \.(css|gif|ico|jpeg|jpg|js|png)$: It serves to turn off logging for static asset requests. Also, since these blocks are typically expensive to serve, it ensures that they are highly cacheable.

After you finish editing, save and close the file. If nano was used, press CTRL+X, Y, and then ENTER.

Having finished with Nginx, proceed to environment variables to pass to your database containers and application at runtime.

Step Two — Defining Environment Variables

Certain environment variables will be needed by your WordPress and database containers when running. This is needed to ensure that your application data is accessible to your application and persistent. The variables include both non-sensitive and sensitive information: sensitive values for application database user and password and your MySQL root password. At the same time,  non-sensitive information includes your application database host and name.

We will set the sensitive values in an .env file and then restrict the circulation of this file instead of setting these values in the main file with information about how our containers will run (our Docker Compose file). This way, these values will be protected from copying over to the repositories of our project and being exposed publicly.

Use ~/wordpress, your main project directory and open a file called .env:

$ nano .env

The secret values included in this file are a password and username used by WordPress to access the database and a password for our MySQL root user.

Add the variable values and names to the file. Make sure to provide your own values for each variable:

~/wordpress/.env
MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_USER=your_wordpress_database_user
MYSQL_PASSWORD=your_wordpress_database_password

Here, a password for the root administrative account and preferred password and username for our application database are included.

When done with editing, save and close the file.

Since your .env file contains private information, include it in your project’s .dockerignore and  .gitignore files. These files tell Docker  and Git what files should not be copied to your Docker images and Git repositories.

Your current working directory should be initialized as a repository with git init in case you want to work with Git for version control:

$ git init

After that, open a .gitignore file:

$ nano .gitignore

Add .env to the file:

~/wordpress/.gitignore
.env

When the editing is done, save and close the file.

To ensure additional security, add .env to a .dockerignore file.

Open the file:

$ nano .dockerignore

Add .env to the file:

~/wordpress/.dockerignore
.env

It is possible to add directories and files associated with the development of your application:

~/wordpress/.dockerignore
.env
.git
docker-compose.yml
.dockerignore

When finished, save and close the file.

Having settled the issue of private information, you can proceed to defining your services in the file docker-compose.yml.

Step Three — Defining Services Using Docker Compose


The service definitions for your setup will be contained in the file docker-compose.yml. In Docker Compose, service is a running container. Service definitions determine how each container will run.

Using Compose, you can define different services to run multi-container applications. With Compose, you can link shared volumes and networks with these services, which will be instrumental for this setup because different containers will be created for our web server, WordPress application, and database. Moreover, a separate container will be created to run the Certbot client to get the webserver certificates.

Open the docker-compose.yml file:

$ nano docker-compose.yml

In order to define your db database service and Compose file version, add the following:

~/wordpress/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

The following options are contained in db service definition:

  • image: due to this command, Compose is informed about what image must be pulled to create the container. Here, we pin the mysql:8.0 image to ensure there will be no future conflicts since the mysql:latest image continues to be updated. To find out more about avoiding dependency conflicts and version pinning, visit the Docker documentation on Dockerfile best practices.
  • container_name: A name for the container is specified by this line.
  • restart: The container restart policy is defined by this line. no is set by default. However, in our example, the container is set to restart if not manually stopped.
  • env_file: It means that environment variables will be added from a file named .env, which is located in our build context (which is our current directory in this case).
  • environment: With this option you can add additional environment variables. The MYSQL_DATABASE variable equal to wordpress will be set to provide a name for the database of our application. SInce this is information is non-sensitive, it can be included in the docker-compose.yml file.
  • volumes: In this case, a volume called dbdata is mounted to the directory /var/lib/mysql on the container. On most distributions, it  is the standard data directory for MySQL.
  • command: With this line, a command to override the default CMD instruction for the image is specified. In this example, an option that starts the MySQL server on the container will be added to the Docker image’s standard mysqld command. It --default-authentication-plugin=mysql_native_password, specifies which authentication mechanism should govern new authentication requests to the server sets and sets the --default-authentication-plugin system variable to mysql_native_password. PHP and our WordPress image will not support MySQL’s newer authentication default, this adjustment has to be made to authenticate the user of our application database.
  • networks: Due to this line, our application service will join the app-network network that e will be defined at the final part of the file.

Below your db service definition, insert the definition for your wordpress application service:

~/wordpress/docker-compose.yml
...
  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

In this service definition the container is named and and a restart policy defined. The same procedure was done with the db service. Also, some specific  options are added:

  • depends_on: Due to this option, our containers will start in order of dependency, which will ensure that the application will start properly.The wordpress container will start after the db container since WordPress application depends on the existence of our application user and database.
  • image: The 5.1.1-fpm-alpine WordPress image is used for this setup. As stated in Step One, this image ensures that the application will have the php-fpm processor required by Nginx to handle PHP processing. The alpine image derives from the Alpine Linux project. This fact will help keep the overall image size down. To find out more about the pros and cons of using alpine images and if it suits your application, read the full discussion under the Image Variants section of the Docker Hub WordPress image page.
  • env_file: Since .env file is where our application database password and user have been defined,in this line, we specify that we want to pull values from our .env file.
  • environment: The values defining our .env file are stated here, but they are assigned to the variable names expected by  the WordPress image: WORDPRESS_DB_PASSWORD and WORDPRESS_DB_USER. A WORDPRESS_DB_HOST is defined here. It will be the MySQL server running on the db container accessible on the default port  3306 of MySQL. Our WORDPRESS_DB_NAME will have the same value that was specified in the service definition of MySQL for our MYSQL_DATABASE: wordpress.
  • volumes: A named volumewordpress is mounted to the /var/www/html mountpoint, which is created by the WordPress image. Such use of named volume allows sharing our application code with other containers.
  • networks: The wordpress container is added to the app-network network.

After that, add the definition for your webserver Nginx service provided below the wordpress application service definition:

~/wordpress/docker-compose.yml
...
  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

As you can see,  our container is given a name. Also, we make it dependent on the wordpress container in order of starting. The alpine image — the 1.15.12-alpine Nginx image is being used.

The following options are included in service definition:

  • ports: With this option, port 80 is exposed to enable the configuration options set in our nginx.conf file in the section "Step One".
  • volumes: A combination of bind mounts and named volumes are defined:
      - wordpress:/var/www/html: The WordPress application code will be mounted to the /var/www/html directory with this option. This directory is the one set as the root in our Nginx server block.
      - ./nginx-conf:/etc/nginx/conf.d: The Nginx configuration directory will bound mounted on the host to the relevant directory on the container.          This will ensure that the container will reflect any changes made to files on the host.
       - certbot-etc:/etc/letsencrypt: The relevant keys for our domain and Let’s Encrypt certificates will be mounted to the appropriate directory on the container with this option.

As you can see, this container is added to the app-network network.

Lastly, add your last service definition for the certbot service below your webserver definition. Do not forget to provide  your own information to replace the domain names and email address from here:

~/wordpress/docker-compose.yml
  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 sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com

Instructions of this definition tell Docker Compose to pull the image certbot/certbot from Docker Hub. The definition also uses named volumes for sharing resources with the Nginx container, as well as the key in certbot-etc, the domain certificates, and the application code in wordpress.

depends_on was used to make the certbot container start when the webserver service is running.

Also, a command option was included. It specifies a subcommand to run with the container’s default command certbot. A certificate with the following options will be obtained by certonly subcommand:

--webroot: Certbot is told to use the plugin webroot to place files in the folder webroot for authentication. HTTP-01 validation method is set for this plugin. It uses an HTTP request as an approval that Certbot is allowed to access resources from a server responding to a domain name that was given.
--webroot-path: The path of the webroot directory is specified by this option.
--email: It specifies the preferred registration and recovery email.
--agree-tos: It means that you agree to Subscriber Agreement of ACME.
--no-eff-email: With this option, you inform Certbot that you prefer not to share your email with the Electronic Frontier Foundation (EFF). You can omit this if you like.
--staging: Certbot is told that Let’s Encrypt’s staging environment is preferred for obtaining test certificates. This option allows you to avoid possible domain request limits by testing your configuration options. To get more information about these limits, check Let’s Encrypt’s rate limits documentation.
-d: With this option, you can specify domain names you want to apply to your request. In our case, www.example.com and example.com have been included. Do not forget to replace them se with your own domain.

Add your volume and network definitions below the certbot service definition:

~/wordpress/docker-compose.yml
...
volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge  

The key of our top-level volumes defines the volumes wordpress, certbot-etc, and dbdata. When volumes are created by Docker, the volume contents are stored in a directory on the host filesystem, which is managed by Docker. This directory is /var/lib/docker/volumes/. Then, the contents of each volume get mounted from this directory to any container using the volume. Due to this option,  sharing code and data between containers is possible.

 The network app-network is user-defined and enables communication between the containers. It is possible bacause they are on the same Docker daemon host. The traffic and communication is streamlined within the application since it does not expose any ports to the outside world due to opening all ports between containers on the same bridge network. Therefore, our wordpress, db, and webserver containers can communicate with each other. Only port 80 needs to be opened for front-end access to the application.

The final docker-compose.yml file will look like shown below:

~/wordpress/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 sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge  

When the editing is finishd, save and close the file.

Having set your service definitions, you can start the containers and test your certificate requests.

Step Four — Obtaining SSL Credentials and Certificates

Our can be launched with the docker-compose up command. This command will create and run our containers in the order that was specified earlier in this tutorial. If our domain requests are successful, the output will return the correct exit status  and the right certificates mounted in the folder /etc/letsencrypt/live on the webserver container.

Now proceed to create the containers using docker-compose up and the -d flag. These will run the wordpress, db, and webserver containers in the background:

$ docker-compose up -d

The output should confirm the creation of your services:

Output
Creating db ... done
Creating wordpress ... done
Creating webserver ... done
Creating certbot   ... done

To check the status of your services, use docker-compose ps:

$ docker-compose ps

If properly configured, wordpress, db, and webserver services will be Up. Also, certbot container exit will return a message with 0 status:

Output
  Name                 Command               State           Ports       
-------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0                      
db          docker-entrypoint.sh --def ...   Up       3306/tcp, 33060/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp 
wordpress   docker-entrypoint.sh php-fpm     Up       9000/tcp      

If the message differs from Up in the State column for thewordpress, db, or webserver services, or an exit status for certbot container is not 0, use the docker-compose logs command to check the service logs:

$ docker-compose logs service_name

Use docker-compose exec to check whether your certificates have been mounted to the webserver container:

$ docker-compose exec webserver ls -la /etc/letsencrypt/live

In case of success regarding your certificate requests, the following output will be returned:

Output
total 16
drwx------    3 root     root          4096 May 10 15:45 .
drwxr-xr-x    9 root     root          4096 May 10 15:45 ..
-rw-r--r--    1 root     root           740 May 10 15:45 README
drwxr-xr-x    2 root     root          4096 May 10 15:45 example.com

Being assured that your request will have a positive result, the certbot service definition to remove the --staging flag can be edited.

Open docker-compose.yml:

$ nano docker-compose.yml

You need to find the section of the file that contains the definition of the certbot service, and insert the --force-renewal flag instead of the --staging flag in the command option. This will inform Certbot that you want to request a new certificate, which will have the same domains as an existing one. The certbot service definition should look as follows:

~/wordpress/docker-compose.yml
...
  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com
...

After that, to recreate the certbot container, run docker-compose up. The --no-deps option will also be included to inform Compose that it can skip starting the webserver service:

$ docker-compose up --force-recreate --no-deps certbot

The successful certificate request will be proved by the following output:

Output
Recreating certbot ... done
Attaching to certbot
certbot      | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot      | Plugins selected: Authenticator webroot, Installer None
certbot      | Renewing an existing certificate
certbot      | Performing the following challenges:
certbot      | http-01 challenge for example.com
certbot      | http-01 challenge for www.example.com
certbot      | Using the webroot path /var/www/html for all unmatched domains.
certbot      | Waiting for verification...
certbot      | Cleaning up challenges
certbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/example.com/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/example.com/privkey.pem
certbot      |    Your cert will expire on 2019-08-08. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      | 
certbot      |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
certbot      |    Donating to EFF:                    https://eff.org/donate-le
certbot      | 
certbot exited with code 0

Having obtained the certificates, you can proceed to modifying the Nginx configuration to include SSL.

Step Five  — How to Modify the Web Server Configuration and Service Definition

In order to enable SSL in Nginx configuration you need to specify your SSL certificate and key locations, add an HTTP redirect to HTTPS, and add security headers and parameters.

Stop your webserver service to include the needed additions:

$ docker-compose stop webserver

First of all, use curl to get the recommended Nginx security parameters from Certbot:

$ 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

With this command, you will save these parameters in a file named options-ssl-nginx.conf, which is located in the nginx-conf directory.

The nextstep is to remove the Nginx configuration file that was created before:

$ rm nginx-conf/nginx.conf

Then open another file version:

$ nano nginx-conf/nginx.conf

The code provided below should be added to the file in order to redirect HTTP to HTTPS and add SSL protocols, credentials, and security headers. Make sure to remove example.com and put your own domain instead:

~/wordpress/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;
        }
}

The webroot for Certbot renewal requests is specified by HTTP server block and is set to the .well-known/acme-challenge directory. A rewrite directive is also included.It directs HTTP requests to the root directory to HTTPS.

ssl and http2 are enabled by the HTTPS server block . It also includes our key locations and SSL certificate, as well as the recommended Certbot security parameters. As you can remember, these parameters have been saved to nginx-conf/options-ssl-nginx.conf.

Also, particular security headers have been included. These will enable us to reach A ratings on the Security Headers server test sites and SSL Labs. These headers include X-Content-Type-Options, X-Frame-Options, Referrer Policy, X-XSS-Protection, and Content-Security-Policy. Note that the HSTS header (HTTP Strict Transport Security) is commented out. You should enable it only if you have assessed its “preload” functionality and perfectly understand the implications.

This block contains our root and index directives, as well as the rest of the location blocks which are WordPress-specific ( these were discussed in Step One).

Save and close the file if you have finished editing.

Before you proceed to recreating the webserver service, add mapping of 443 port to your webserver service definition.

Thus, open your docker-compose.yml file:

$ nano docker-compose.yml

Add the port mapping provided below to the webserver service definition:

~/wordpress/docker-compose.yml
...
  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

The docker-compose.yml file should look like shown below:

~/wordpress/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"
      - "443:443"
    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 sammy@example.com --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge  

When the editing is finished, save and close the file.

Recreate the webserver service:

$ docker-compose up -d --force-recreate --no-deps webserver

Use docker-compose ps to check your services:

$ docker-compose ps

The output like the one below indicates that your wordpresswebserver, and db services are active:

Output
  Name                 Command               State                     Ports                  
----------------------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0                                           
db          docker-entrypoint.sh --def ...   Up       3306/tcp, 33060/tcp                     
webserver   nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
wordpress   docker-entrypoint.sh php-fpm     Up       9000/tcp    

Now, your containers are running, and you can complete the installation of WordPress through the web interface.

Step Six — Installation Through the Web Interface

Use WordPress web interface to finish the installation.

Use the web browser of your choice to navigate to your the domain of your service. Make sure to replace example.com with your own domain name:

https://example.com

Choose the language:

WordPress Language Selector

Click Continue to pass to the main setup page. Here, you will need to choose a name for your website, as well as a username. We recommend choosing a strong password and a memorable username (rather than “admin”). WordPress generates the password automatically, so you can use it or create your own.

Lastly, you will need to provide your email address. You also need to decide if you want to prohibit search engines from indexing your site:

WordPress Main Setup Page

If you click on Install WordPress , you will be taken to a login prompt:

WordPress Login Screen

Log in to get access to the WordPress administration dashboard:

WordPress Main Admin Dashboard

Now your WordPress installation is complete. What is left is to ensure that your SSL certificates will be automatically renewed when needed.

Step Seven — Renewing Certificates

The certificates of Let’s Encrypt remain valid for 90 days; therefore, it is better to set up an automated renewal process. This will ensure that the certificates do not lapse. You need to create a job with the cron scheduling utility. In our example, a cron job, which will periodically run a script renewing our certificates and reloading Nginx configuration, will be created.

Open a script ssl_renew.sh:

$ nano ssl_renew.sh

Add the code provided below to the script. The code will serve to reload your web server configuration after renewing your certificates. Make sure to remove the example username here and insert your own non-root username:

~/wordpress/ssl_renew.sh
#!/bin/bash

COMPOSE="/usr/local/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

FIrst of all, this script assigns the docker-compose binary to a variable named COMPOSE. It also specifies the --no-ansi option. This option will run docker-compose commands without using ANSI control characters. The same concerns the docker binary. Lastly, it changes to the ~/wordpress project directory and runs the docker-compose commands provided below:

  • docker-compose run: The command will start a certbot container. Also, it will override the command provided in our certbot service definition. Here, the renew subcommand is used instead of the certonly subcommand. It will renew the expiring certificates. To test our script, the --dry-run option was included.
  • docker-compose kill: The command serves to send a SIGHUP signal to the webserver container in order to reload the configuration of Nginx. To find out more about using this process for reloading your Nginx configuration, visit Docker blog post on deploying the official Nginx image with Docker.

Then, docker system prune is run to remove the unused images and containers.

When the editing is finished, close the file. Make it executable:

$ chmod +x ssl_renew.sh

After that, open your root crontab file. This is done to run the renewal script at a particular interval:

$ sudo crontab -e 

If you edit this file for the first time, the system will ask to choose an editor:

Output
no crontab for root - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/nano        <---- easiest
  2. /usr/bin/vim.basic
  3. /usr/bin/vim.tiny
  4. /bin/ed

Choose 1-4 [1]:
...

Add the line provided below to the bottom of the file:

crontab
...
*/5 * * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

The job interval will be set to every five minutes. This way, it will be possible to test whether your renewal request has worked as intended. The files, such as a cron.log and log file have been created to record relevant output from the job.

After five minutes, cron.log should be checked to see if the renewal request was successful:

$ tail -f /var/log/cron.log

The output like the one below informs about a successful renewal:

Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Now, the crontab file can be modified to set a daily interval. For example, if you want to run the script every day at noon, modify the last line of the file as shown below:

crontab
...
0 12 * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

Remove the --dry-run option from your ssl_renew.sh script:

~/wordpress/ssl_renew.sh
#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/wordpress/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Your cron job will renew your Let’s Encrypt certificates when they are eligible, thus ensuring that they don’t lapse. It is also possible to set up log rotation with the Logrotate utility to compress and rotate your log files.

 

 

 

 

  • NGIN, WORDPRESS, MYSQL, DOCKER, LET'S ENCRYPT, UBUNTU 18.04
  • 0 Users Found This Useful
Was this answer helpful?

Related Articles

How To Install Linux, Apache, MySQL, PHP (LAMP) stack on Ubuntu 18.04

Introduction ‘LAMP” is a stack of open-source software. Typically, it is installed together in...

How To Install Linux, Nginx, MySQL, PHP (LEMP stack) on Ubuntu 18.04

Introduction LEMP is a pack of software. It is generally used to serve dynamic web applications...

How To Install Nginx on CentOS 7

What is Nginx Nginx is server software that is characterized by high performance, flexibility,...

How To Install Node.js on Debian 10

Introduction Node.js a Javascript platform that allows easy creation of networked applications...

How To Install Node.js on Ubuntu 18.04

Introduction Node.js is a JavaScript platform that is used to allow users to build network...