Skip to content

Wordpress (no DB server included) running with Nginx in a Docker container with caching and encryption enabled

License

Notifications You must be signed in to change notification settings

rija/docker-nginx-fpm-caches-wordpress

Repository files navigation

docker-nginx-fpm-caches-wordpress

Build Status Build Status

Maintainer

Rija Ménagé

Description

Dockerfile to create a container with Nginx and php-fpm running a Wordpress web application. TLS encryption is provided (and automatically renewed) using free certificates provided by Let's Encrypt. Page caching (using Nginx's FastCGI cache) and Opcode caching with Zend Opcache are enabled and configured.

The container can be deployed with either a vanilla installation of Wordpress or an existing Wordpress-based codebase.

The container doesn't have a database server, but the supplied docker compose file allow instantiating a MariaDB 10.2 database server on the same network as the Wordpress container.

When choosing to use an existing Wordpress-based web site, the codebase is baked into the image when the image is built, so that the deployed container is immutable, making it play nicely in a versioned deployment pipelines.

When choosing to use a vanilla installation of Wordpress, the latest wordpress software is baked at build time, and additional security and caching related plugins are installed during deployment of the container.

Headline features:

  • Nginx 1.13.0
  • HTTP/2 and TLS encryption configured
  • TLS configured using Mozilla Server-side TLS Intermediate profile + TLSv1.3
  • PHP 7.1 installed with CLI, PHP-FPM and bare essential extensions
  • FastCGI Caching+Cache Purge and Zend Opcode enabled
  • RealIP Nginx module installed for when running behind a reverse-proxy
  • Latest version of Wordpress is installed at container startup
  • Can clone a Wordpress-based site from GIT repositories
  • WP-CLI to manage a Wordpress install from command line
  • OS-level security updates performed automatically
  • TLS certificate automatically renewed
  • Daily backup of database to volume sharable with Docker host
  • Supervisord 3.0 as init script to manage processes' life-cycle
  • Small-footprint Docker image using Bitnami/minideb as BASE image

Available Docker Hub tags: v1, v2, latest

How to run

with docker run:

$ docker run --name a-mariadb-server \
	-e MYSQL_ROOT_PASSWORD=my-secret-pw \
	-e MYSQL_USER=wp_user \
	-e MYSQL_PASSWORD=wp_password \
	-e MYSQL_DATABASE=wp_database \
	-d mariadb:10.2

$ docker run -d \
	--link a-mariadb-server:dbserver \
	--name a-wordpress-container \
	-e SERVER_NAME=example.com \
	-e [email protected] \
	-e ADMIN_PASSWORD=changemenow \
	-e DB_HOSTNAME=dbserver \
	-e DB_USER=wp_user \
	-e DB_PASSWORD=wp_password \
	-e DB_DATABASE=wp_database \
	-v /etc/letsencrypt:/etc/letsencrypt \
	-v /${HOME}:/root/sql \
	-p 443:443 -p 80:80 \
	rija/docker-nginx-fpm-caches-wordpress

Notes: The ADMIN_EMAIL variable is used by WP-CLI for the initial setup of the Wordpress install and by Let's Encrypt's Certbot for managing TLS certificates renewal. It is also supplied alongside ADMIN_PASSWORD to the Wordpress install associated with the admin user.

with Docker compose:

$ cd docker-nginx-fpm-caches-wordpress
$ ./make_env
$ docker-compose up -d

One can adjust the values in the .env file updated (and created if non-existent) by ./make_env

with Ansible playbook:

- New image based on vanilla Wordpress pushed to a private registry
$ ansible-playbook --extra-vars="registry_url=registry.gitlab.com registry_user=foobar force_build=yes download_wp=yes" ansible/press-site.yml

One can adjust the values in the .env file updated (and created if non-existent) by ./make_env

- Deploy the previously baked image to a Digital Ocean droplet
$ ansible-playbook -i digital_ocean.py  --extra-vars="registry_url=registry.gitlab.com registry_user=foobar docker_host_user=docker" ansible/deploy-site.yml

where digital_ocean.py is downloaded from https://github.com/ansible/ansible/blob/devel/contrib/inventory/digital_ocean.py

$ curl -O https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/digital_ocean.py
$ chmod u+x digital_ocean.py

if you don't deploy on Digital Ocean, you can find the relevant dynamic inventory for your cloud service on https://github.com/ansible/ansible/tree/devel/contrib/inventory

- Workflow for an existing web site

make sure you have the web site in a directory called wordpress inside the website directory. Then ensure the database dump to be imported is there as well under the name wordpress.sql:

website/
├── README.md
├── wordpress
├── VERSION
└── wordpress.sql

Then ensure you have an .env file with appropriate variables:

$ ./make_env

The script above will also keep the build specific variables up-to-date.

Now, you can bake an image and upload it to a registry

$ ansible-playbook --extra-vars="registry_url=registry.gitlab.com registry_user=foobar force_build=yes" ansible/press-site.yml

to deploy, use:

$ ansible-playbook -vvv   -i digital_ocean.py  --extra-vars="registry_url=registry.gitlab.com registry_user=foobar docker_host_user=someuser update_image=yes" ansible/deploy-site.yml

Note: There is a known issue, such that sometimes the install script will fails to load the database dump and the docker logs will show the following error:

install_wordpress stdout | this is an existing Wordpress web site, loading the database dump if not loaded already ...
install_wordpress stderr | ERROR install_wordpress stderr |  2003 (HY000) install_wordpress stderr | : Can't connect to MySQL server on 'dbs' (111)
2018-04-27 16:18:14,558 INFO exited: install_wordpress (exit status 1; not expected)

When that happens, simply re-run the installation script /install_wordpress and the database will be loaded correctly. There is an issue raised for this: #11

How to enable Encryption (TLS)

This step is not necessary if you used the ansible playbook above.

It is advised to have read Lets Encrypt's FAQ and user guide beforehand.

after the Wordpress container has been started, run the following command on the host and follow the on-screen instructions:

$ docker exec -it a-wordpress-container bash -c "/setup_web_cert"

After the command as returned with a successful message regarding acquisition of certificate, nginx will be reloaded with encryption enabled and configured.

Notes:

  • There is no change needed to nginx configuration for standard use cases
  • Navigating to the web site will throw a connection error until that step has been performed as encryption is enabled across the board and http connections are redirected to https. You should update nginx configuration files as needed to match your use case if that behaviour is not desirable.
  • Lets Encrypt's' Certbot client configuration file is deployed to /etc/letsencrypt/cli.ini. Review and amend that file according to needs.
  • the generated certificate is valid for domain.tld and www.domain.tld (SAN)
  • The certificate files are accessible on the Docker host server in /etc/letsencrypt

How to build

First, make sure there is a Wordpress codebase under the website/ directory. Check the website/README.md for more details.

$ cd docker-nginx-fpm-caches-wordpress
$ ./make_env && docker-compose up --build -d

One should adjust the values in the .env file updated (and created if non-existent) by ./make_env make_env should be executed at every build so that the dynamic docker labels for build date and vcs ref are populated accurately.

How to login to Wordpress Dashboard

The user is admin and the initial password can be supplied as ADMIN_PASSWORD in the .env file generated by ./make_env

License

MIT (see the LICENSE file)

Credits

  • Eugene Ware for the original work on a Nginx/Wordpress Dockerfile, whose ideas I've extended upon in this project
  • @renchap and @DrPain from Let's Encrypt Community, whose ideas put me on the path of a working and elegant solution for Nginx/LetsEncrypt integration
  • Bjørn Johansen for his blog articles on hardening a Wordpress installation that informed some of the choices I made
  • Rahul Bansal of EasyEngine for his tutorials on Nginx/Wordpress integration that informed some of the choices I made
  • All the contributors to the Wordpress.org's Nginx support page
  • Mozilla for their awesome SSL configuration generator
  • All the the other people whose blog articles I've directly added in the comments in the relevant artefacts of this project