How this Blog was created !

About this tutorial

This article will provide you all the needed steps to create from scratch a public Website based on WordPress with security SSL enabled. It is based on my own experience with this Blog and I hope it will be helpful for those who need to troubleshoot their own installation.

Main Steps for this Blog creation

Figure1: Blog creation main steps

Step 1: Buy a virtual private Server

If you want to have your own blog on internet like me, first step will be to buy a small virtual private server in the cloud. There are many cloud providers (AWS, Microsoft Azure, Google Cloud, …). The one I chose for this Blog is OVH as it is the cheapest solution for my usage (6$ per month).

Figure2: OVH VPS pricing

Note:
You can choose the location of your server by clicking on the World map icon:

Figure3: OVH location

Step 2: Buy a domain name

You will need also to buy a domain name which will allow people to visit your website. I also used OVH to buy my domain name for this blog. Just be careful with the name and the extension you will chose as the prices can vary a lot (from 10$ to 5000$ per year).

Figure4: Domain name selection

Step 3: Install a linux OS on your virtual private Server

It’s an easy task as it is done by your cloud provider during the configuration of your own virtual private server.
For this Blog, I chosed Ubuntu but you can use another OS system like Fedora, Centos or Debian depending on your own preferences.

Figure5: OVH VPS configuration

Step 4: Install docker and docker-compose on your virtual server

The only software you will need to install and run on your server is Docker and Docker-compose.
Docker is an open platform for developing, shipping, and running applications very quickly inside linux containers.
Docker-Compose is an orchestrator which manages, scales and maintains containerized applications.
Please refer to the following official documentation for the installation of Docker :

The following example provides the linux commands to run to install docker and docker-compose tools on your Ubuntu OS :

# Setup Docker Repository
$ sudo apt update
$ sudo apt-get 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  $(lsb_release -cs)  stable"

# Install Docker Engine
$ sudo apt update
$ sudo apt-get install docker-ce
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo systemctl status docker
$ docker -v
==> Docker version 20.10.10, build b485636

# installation docker-compose
$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.0.1/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose version
==> Docker Compose version v2.0.1

# Allow Non-root users to run Docker Commands
$ sudo groupadd docker
$ sudo usermod -aG docker admin

Step 5 : Create nginx container

Nginx is a Web Server which can run as a proxy server to enable https connection between a client browser and your Web site. It needs SSL certificates to establish a secure encrypted connection. They can be generated automatically by Let’s Encrypt tool which is an open source, completely free Certificate Authority (CA). The SSL certificates are valid for 90 days and need to be renewed after this delay (renewal is also free).

The following schema shows how Nginx manage a secure connection with Let’s Encrypt :

Figure6: nginx ssl implementation

To create a nginx container, you can follow the official nginx docker installation here :

In my case, I created my own nginx container in order to install both nginx and certbot tools in the same container.
You can download all the needed files for your website creation directly from GitHub :

After downloading all the needed files on your server, you should get the following tree :

# command to run from your private virtual server after downloading all the files 
# (the root directory is /lab/projet01 but you can change it)

$ cd /lab/projet01  
$ tree -L 5
+-- docker-code
¦   +-- nginx
¦   ¦   +-- 1.21
¦   ¦      +-- config
¦   ¦      ¦   +-- website-index.html
¦   ¦      ¦   +-- website-nginx-http.conf
¦   ¦      ¦   +-- website-nginx-https.conf
¦   ¦      ¦   +-- website-nginx-https-proxypass-wordpress.conf
¦   ¦      ¦   +-- website-sites-available.domain
¦   ¦      +-- Dockerfile
¦   ¦      +-- README.md
¦   ¦      +-- scripts
¦   ¦      ¦   +-- docker-create-user.sh
¦   ¦      ¦   +-- docker-entrypoint.sh
¦   ¦      ¦   +-- docker-healthcheck.sh
¦   ¦      ¦   +-- docker-install-software.sh
¦   ¦      +-- software
¦   +-- nginx-http-docker-compose.yml
¦   +-- nginx-https-docker-compose.yml
¦   +-- wordpress-mysql-docker-compose.yml
¦   +-- wordpress-mysql-nginx-docker-compose.yml
+-- docker-data
    +-- data-mysql
    ¦   +-- myblog.fr
    +-- data-nginx
    ¦   +-- myblog.fr
    +-- data-shared
    +-- data-wordpress
        +-- myblog.fr

Files / Directories description :

  • nginx-http-docker-compose.yml: compose file to start nginx container in unsecure http mode
  • nginx-https-docker-compose.yml: compose file to start nginx container in secure http mode
  • wordpress-mysql-docker-compose.yml: compose file to start wordpress in insecure mode
  • wordpress-mysql-nginx-docker-compose.yml: docker file to start wordpress in secure mode
  • Dockerfile: docker file to define the content of nginx container
  • docker-create-user.sh: bash script to create a none-root user
  • docker-entrypoint.sh: bash script to start the container
  • docker-healthcheck.sh: bash script to check the heath of the container
  • docker-install-software.sh: bash script to install nginx and certbot tools
  • website-index.html: nginx html file to test the container
  • website-nginx-http.conf: nginx config file
  • website-sites-available.domain: nginx virtual host config file
  • data-shared: directory to share data between your nginx container and the server
  • data-mysql: directory to store mysql database
  • data-nginx: directory to store nginx data
  • data-wordpress: directory to store wordpress data

Here is the content of the nginx-http-docker-compose.yml

version: '3.8'

services:


# ----------------------------------------------
# nginx 1.21 : container creation
# ----------------------------------------------
 nginx:
  build:
   context: nginx/1.21
   args:
    USER_UID: 1010
    GROUP_GID: 1010
    UNAME: 'webadmin'
  image: lab/nginx:1.21
  hostname: nginx
  container_name: 'nginx'
  environment:
    NGINX_SETUP: 'http'
    NGINX_WEBSITE: 'myblog.fr'
    DOCKER_DEBUG_FLAG: 'no'
  healthcheck:
       test: ['CMD', '/opt/nginx/scripts/docker-healthcheck.sh']
       timeout: 5s
       interval: 30s
       start_period: 10s
       retries: 3
  tty: true
  networks:
     - lab
  restart: 'always'
  ports:
   - '80:80'
  volumes:
    - volume-data-shared:/opt/projet01/data-shared
    - /lab/projet01/docker-data/data-nginx/myblog.fr:/var/www/html




# ----------------------------------------------
# network definition
# ----------------------------------------------
networks:
   lab:
     driver: bridge

# ----------------------------------------------
# volume creation for persistant data
# ----------------------------------------------
volumes:
  volume-data-shared:
    driver: local
    driver_opts:
      o: bind
      type: none
      device: /lab/projet01/docker-data/data-shared

To create and start your nginx container, update first the above compose file and change the variable “NGINX_WEBSITE” to point to your own domain name. Then you will have to run the following docker commands :

# commands to run from the directory you created on your own server :
# start nginx container listening to port 80

$ cd /lab/projet01/docker-code
$ docker-compose -f nginx-http-docker-compose.yml up -d --build

# check that your nginx is working (status=healthy)
$ docker ps

CONTAINER ID   IMAGE            STATUS                 PORTS                    NAMES
-------------------------------------------------------------------------------------
c321637b40c6   lab/nginx:1.21   Up 4 hours (healthy)   0.0.0.0:80->80/tcp,...   nginx

# another check to confirm that you can run commands inside your nginx container 
$ docker exec -it nginx bash
$ sudo netstat -a|grep 80

Proto Recv-Q Send-Q Local Address           Foreign Address         State      
--------------------------------------------------------------------------
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp6       0      0 [::]:80                 [::]:*                  LISTEN 

If the above checks confirm that your nginx container is running correctly, next step will be to update your domain name to point to the IP address of your virtual private server. If you are using OVH cloud provider, you can set it up by going to your OVH Dashboard, clicking on your domain name and updating the DNS zone (A rows) as follow :

Figure7: DNS zone to update

You can now test that your own website can be reached everywhere by opening a browser with the following URL based on the domain name you bought :

  • http://myblog.fr

You should get the following screen :

Figure8: blog access in unsecure http mode

Step 6 : Setup Let’s Encrypt (https) with nginx

To switch your existing none secure HTTP Web site (ex: http://myblog.fr) to a secure HTTPS website (ex: https://myblog.fr), the easiest way is to use Certbot tool which will fetch automatically the needed SSL certificates from Let’s Encrypt. As a reminder, Let’s Encrypt is a non-profit certificate authority that provides X. 509 certificates for Transport Layer Security (TLS) encryption at no charge.

To run successfully certbot, you will need :

  • Your HTTP website online with an open port 80
  • your Domain name pointing to your Virtual Private Server
  • An access to the nginx container command line
  • The ability to run sudo command

If all these prerequisites are met, you will be able to implement https as follow :

# commands to run from your private virtual server :

$ docker exec -it nginx bash
$ sudo certbot --nginx -d myblog.fr -d www.myblog.fr --rsa-key-size 4096 --agree-tos --no-eff-email

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): youremail@gmail.com
Account registered.
Requesting a certificate for myblog.fr and www.myblog.fr
Performing the following challenges:
http-01 challenge for myblog.fr
http-01 challenge for www.myblog.fr
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/nginx.conf
Deploying Certificate to VirtualHost /etc/nginx/nginx.conf
Redirecting all traffic on port 80 to ssl in /etc/nginx/nginx.conf
Redirecting all traffic on port 80 to ssl in /etc/nginx/nginx.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://myblog.fr and
L’info selon Myblog
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/myblog.fr/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/myblog.fr/privkey.pem Your certificate will expire on 2022-02-16. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le # Copy SSL certificates to nginx "config" directory $ cp /etc/nginx/nginx.conf /opt/projet01/data-shared $ sudo cp -r /etc/letsencrypt /opt/projet01/data-shared $ exit $ cd /lab/projet01/docker-data/data-shared $ sudo tar cvzf letsencrypt-myblog-fr.tar.gz letsencrypt $ cp letsencrypt-myblog-fr.tar.gz /lab/projet01/docker-code/nginx/1.21/config/ $ cp nginx.conf /lab/projet01/docker-code/nginx/1.21/config/

Note:
To avoid permission issues during copy commands, it is recommended to map the container’s user/group to an existing user/group on your server. This can be done by creating a specific user on your server and to update your docker-compose files accordingly with the following arguments:

  • USER_UID
  • GROUP_GID
  • UNAME

To create a user (ex: webadmin, UID=1010, GID=1010) on your Ubuntu or Debian server, you can use the following commands :

# commands to run on your virtual private server to create a user
# (on Centos,  use "useradd" command instead of "adduser")

$ sudo groupadd -g 1010 webadmin
$ cat /etc/group|grep 1010
$ sudo adduser --gid 1010 --uid 1010 webadmin

# check that the user has been created
$ cat /etc/passwd|grep 1010
==>   webadmin:x:1010:1010:webadmin,,,:/home/webadmin:/bin/bash

To enable https for your web site, you just need to stop the current nginx container listening to port 80 and to restart it with the needed SSL certificates and with the opened port 443. This can be done by using the following docker-compose file “nginx-https-docker-compose.yml”

version: '3.8'

services:


# ----------------------------------------------
# nginx 1.21 : container creation
# ----------------------------------------------
 nginx:
  build:
   context: nginx/1.21
   args:
    USER_UID: 1010
    GROUP_GID: 1010
    UNAME: 'webadmin'
  image: lab/nginx:1.21
  hostname: nginx
  container_name: 'nginx'
  environment:
    NGINX_SETUP: 'https'
    NGINX_WEBSITE: 'myblog.fr'
    DOCKER_DEBUG_FLAG: 'no'
  healthcheck:
       test: ['CMD', '/opt/nginx/scripts/docker-healthcheck.sh']
       timeout: 5s
       interval: 30s
       start_period: 10s
       retries: 3
  tty: true
  networks:
     - lab
  restart: 'always'
  ports:
   - '443:443'
  volumes:
    - volume-data-shared:/opt/projet01/data-shared
    - /lab/projet01/docker-data/data-nginx/myblog.fr:/var/www/html



# ----------------------------------------------
# networks
# ----------------------------------------------
networks:
   lab:
     driver: bridge

# ----------------------------------------------
# volume creation for persistent data
# ----------------------------------------------
volumes:
  volume-data-shared:
    driver: local
    driver_opts:
      o: bind
      type: none
      device: /lab/projet01/docker-data/data-shared

Here are the commands to restart your nginx container in secure https mode :

# commands to run from the directory you created on your own private virtual server :
# stop previous running nginx container listening to port 80
$ cd /lab/projet01/docker-code
$ docker-compose -f nginx-http-docker-compose.yml down

# start nginx container listening to port 443
$ docker-compose -f nginx-https-docker-compose.yml up -d --build

# check that nginx is listening to port 443
$ docker ps
CONTAINER ID   IMAGE            STATUS    PORTS                    NAMES
-------------------------------------------------------------------------
6d981412e8be   lab/nginx:1.21   healthy   0.0.0.0:443->443/tcp,... nginx


$ docker exec -it nginx bash
$ netstat -a|grep 443
Proto Recv-Q Send-Q Local Address           Foreign Address         State
--------------------------------------------------------------------------
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN
tcp6       0      0 [::]:443                [::]:*                  LISTEN

After this last command, you can test that your Web site can be reached from https via the following URL based on the domain name you bought :

  • https://myblog.fr
Figure9: Blog access in secure https mode

You can also test the security of your Web Site by using a free online analysis tool like “SSL Labs” :

Figure10: Blog security checks

Note:
If you just need to create a static web site, you can stop here and add all your html pages in the following shared nginx directory:

  • /lab/projet01/docker-data/data-nginx/myblog.fr

Step 7 Install Mysql + WordPress

An easy way to install mysql and wordpress docker containers is to use the official wordpress docker-compose file :

Here is the content of the compose file “‘wordpress-mysql-docker-compose.yml” you can use it to create your own mysql and wordpress containers :

version: '3.8'
services:

# ----------------------------------------------
# mysql 5.7 : container creation 
# ----------------------------------------------
 mysql:
  image: mysql:5.7
  hostname: mysql
  container_name: 'mysql'
  environment:
      MYSQL_ROOT_PASSWORD: passwordtochange01
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: passwordtochange02
  links:
   - debian
  depends_on:
   - debian
  tty: true
  networks:
     - lab
  restart: 'always'
  ports:
   - '3306:3306' 
  volumes:
   - /lab/projet01/docker-data/data-shared:/opt   


# ----------------------------------------------
# wordpress 5 : container creation 
# ----------------------------------------------
 wordpress:
  image: wordpress:latest
  hostname: wordpress
  container_name: 'wordpress'
  links:
   - mysql
  depends_on:
      - mysql
  tty: true
  networks:
     - lab
  restart: always
  ports:
    - '8000:80'
  environment:
      WORDPRESS_DB_HOST: mysql:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: passwordtochange02
      WORDPRESS_DB_NAME: wordpress
  volumes:
   - /lab/projet01/docker-data/data-wordpress/myblogfr:/var/www/html  
   - /lab/projet01/docker-data/data-shared:/opt   


# ----------------------------------------------
# networks
# ----------------------------------------------
networks:
   lab:
      driver: bridge

Notes :

  • I have customized the official docker compose file by adding some permanent volumes. This will allow you to quickly backup your own website by just saving the content of these volumes.
  • you can do more customizations by using your own Dockerfile and docker-entrepoint.sh files for wordpress and mysql containers. You can get the official content of these files on the wordpress docker hub by clicking on the version of the wanted version of wordpress.
  • if needed, you can also add a phpmyadmin container to manage your mysql database. this can be done by using the official docker compose file you can get here : https://hub.docker.com/r/phpmyadmin/phpmyadmin/

Step 8 Start WordPress

First step is to create and start the mysql and wordpress containers without your secure nginx container in order to check that they are working alone correctly :

# commands to run from the directory you created on your own server :

# creation of permanent volumes for mysql and wordpress 
# (change myblogfr directory name by your own domain name)
$ cd /lab
$ mkdir -p projet01/docker-data/data-mysql/db
$ mkdir -p projet01/docker-data/data-wordpress/myblogfr

# start containers in insecure mode
$ cd /lab/projet01/docker-code
$ docker-compose -f wordpress-mysql-docker-compose.yml up -d --build

# check that your wordpress and mysql are working
$ docker ps

CONTAINER ID   IMAGE            STATUS                 PORTS                NAMES
-------------------------------------------------------------------------------------
bbf5cfa68e03   wordpress:latest Up 11 minutes   0.0.0.0:8000->80/tcp,...    wordpress

45efb23e3910   mysql:5.7        Up 11 minutes   0.0.0.0:3306->3306/tcp,...  mysql

To ensure that mysql and wordpress are running successfully, another check is to open a browser with your own domain name on port 8000 :

You should get the following wordpress setup screen :

Figure 11: WordPress access in unsecure http mode

Next step is to link your nginx container to your wordpress container in a secure way. This can be done by creating a dedicated nginx config file “website-nginx-https-proxypass-wordpress.conf” with proxypass feature as follow :

events {
  worker_connections  4096;  ## Default: 1024
}
http {
 server {

    server_name  XXX www.XXX;

   # location / {
   #     root   /var/www/html;
   #     index  index.html index.htm;
   # }
    index index.php index.html index.htm;
    location / {
        proxy_pass http://wordpress;
        proxy_redirect          off;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Forwarded-Host $server_name;
        proxy_set_header        X-Forwarded-Proto https;
        proxy_set_header        X-Forwarded-Port 443;
        proxy_read_timeout      86400;
        proxy_buffering off;
    }
    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/XXX/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/XXX/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
 server {
    if ($host = www.XXX) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    if ($host = XXX) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    listen      80;
    listen      [::]:80;
    server_name  XXX www.XXX;
    return 404; # managed by Certbot
}}

Last step is to merge all your docker compose files in one single docker-compose.yml file as follow

version: '3.8'
services:

# ----------------------------------------------
# mysql 5.7 : container creation 
# ----------------------------------------------
 mysql:
  image: mysql:5.7
  hostname: mysql
  container_name: 'mysql'
  environment:
      MYSQL_ROOT_PASSWORD: passwordtochange01
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: passwordtochange02
  links:
   - debian
  depends_on:
   - debian
  tty: true
  networks:
     - lab
  restart: 'always'
  volumes:
   - /lab/projet01/docker-data/data-mysql/db:/var/lib/mysql  
   - volume-data-shared:/opt   

# ----------------------------------------------
# wordpress 5 : container creation 
# ----------------------------------------------
 wordpress:
  image: wordpress:latest
  hostname: wordpress
  container_name: 'wordpress'
  links:
   - mysql
  depends_on:
      - mysql
  tty: true
  networks:
     - lab
  restart: always
  environment:
      WORDPRESS_DB_HOST: mysql:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: passwordtochange02
      WORDPRESS_DB_NAME: wordpress
  volumes:
   - /lab/projet01/docker-data/data-wordpress/myblogfr:/var/www/html  
   - volume-data-shared:/opt  
   
# ----------------------------------------------
# nginx 1.21 : container creation
# ----------------------------------------------
 nginx:
  build:
   context: nginx/1.21
   args:
    USER_UID: 1010
    GROUP_GID: 1010
    UNAME: 'webadmin'
  image: lab/nginx:1.21
  hostname: nginx
  container_name: 'nginx'
  environment:
    NGINX_SETUP: 'proxypass'
    NGINX_WEBSITE: 'myblog.fr'
    DOCKER_DEBUG_FLAG: 'no'
  healthcheck:
       test: ['CMD', '/opt/nginx/scripts/docker-healthcheck.sh']
       timeout: 5s
       interval: 30s
       start_period: 10s
       retries: 3
  tty: true
  links:
   - wordpress
   - debian
  networks:
     - lab
  restart: 'always'
  ports:
   - '443:443'
  volumes:
    - volume-data-shared:/opt/projet01/data-shared
    

# ----------------------------------------------
# networks
# ----------------------------------------------
networks:
   lab:
     driver: bridge

# ----------------------------------------------
# volume creation for persistent data
# ----------------------------------------------
volumes:
  volume-data-shared:
    driver: local
    driver_opts:
      o: bind
      type: none
      device: /lab/projet01/docker-data/data-shared

Notes about the above docker compose file :

  • The insecure ports directive has been removed from both wordpress and mysql containers definition
  • Don’t forget to change the NGINX_WEBSITE variable to your own domain name
  • The NGINX_SETUP is set to proxypass to link nginx and wordpress containers

To start wordpress in a securely manner, you just need to run the following linux commands :

# commands to run from the directory you created on your own server :


# stop previous containers
docker-compose -f wordpress-mysql-docker-compose.yml down
docker-compose -f nginx-https-docker-compose.yml down

# start all containers in secure mode
$ cd /lab/projet01/docker-code
$ docker-compose -f docker-compose.yml up -d --build

# check that all your containers are working
$ docker ps

CONTAINER ID   IMAGE            STATUS                 PORTS                NAMES
-------------------------------------------------------------------------------------
bbf5cfa68e03   wordpress:latest Up 11 minutes   80/tcp                      wordpress

45efb23e3910   mysql:5.7        Up 11 minutes   3306/tcp                    mysql

b14a7b5267f7   lab/nginx:1.21   healthy         0.0.0.0:443->443/tcp, ...   nginx

You should now be able to start your website based on wordpress in a secure mode https :

You should get the following wordpress setup screen :

Figure 12: WordPress access in secure https mode

It’s the last step: You just need to click on the continue button and setup now your own secure worpress website !

Conclusion

That’s all Folks !
This installation is quite basic. you can go further by adding a local inventory to store all your docker images. You can also add a phpmyadmin container or customize your wordpress/mysql containers.

So, If you need to add some more details, please feel free to comment this article…

Enjoy !

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.