Periodically I deploy simple web sites with Nginx, PHP and MySQL. It is usually one or two virtual servers, all with similar requirements and configs. I came to a "setup-and-forget" set of configs to deploy a site and upgrade in a few years.
Functional requirements:
- HTTPS with ACME-issued certificate (Let'sEncrypt, BuyPass, and others)
- Local deployment with a self-issued certificate
- Nginx with HTTP2
- IPv6 support with a single IP, as most virtual servers have
- Simple upgrade and migration of services among server providers
- Use environment variables for secrets such as a database password
- Healthcheck for services
- Works in Linux as production environment
- Works in Docker Desktop for both Mac and Windows as development environment
Non-functional requirements:
- Lightweight to fit a cheap virtual server with 1Gb RAM.
- Use official well-known repositories and images, no third-party dependencies
- Single site per server. VPS are cheap enough. It can serve multiple domains, of course.
Solution on GitHub://grikdotnet/docker-patterns/nginx-acme-php-mysql
Why Docker Swarm?
Kubernetes could be a great choice, but it consumes gigabytes of RAM. It just won't work in a tiny VPS. Ansible/Vagrant are popular tools among system administrators, but they don't provide service decoupling. I like upgrading PHP, Nginx and MySQL by downloading new images for containers with a single command.
Problems:
- Docker Swarm does not provide cron jobs scheduling. ACME renewal should be done each 2 months. I have a dedicated article describing this solution.
- Swarm does not support IPv6 options, and Docker documentation asks for a /80 IPv6 range.
* You can see an "acme.sh"
package used instead of Certbot. Acme.sh in the "Nginx mode" configures
Nginx to issue certificates. This way certificates can be obtained
before starting Nginx with production configs. Local deployment generates a self-signed certificate.
* Recent Nginx and MySQL images support init scripts in "/docker-entrypoint.d/". You can see scripts mounted to the Nginx and MySQL containers installing openssl and acme.sh packages, initializing database when container starts. This way you can avoid maintaining a custom image.
* There is no simple solution to work with IPv6 in Docker Swarm. Docker uses IPv4 NAT to route traffic, and IPv6 is just something Docker not designed for.
Docker documentation offers is to use Compose-file version 2, ask a provider for a /80 range, and set the "enable_ipv6" flag for the docker engine. This requires too much manual work.
Another way is to add an IPv6 NAT service in a Docker container. But the best way I found is to run Nginx in a "host" network.
* A host network can't be used in Docker desktop in Windows and Mac. This is
solved with YAML inheritance in a
"docker-compose.override.yml" file.
A local deployment with
a "docker-compose up" uses both "docker-compose.yml" and
"docker-compose.override.yml" files.
In production the command "docker stack up -c docker-compose.yml mystack" reads just the "docker-compose.yml" file and runs Nginx in a host network mode.
In production the command "docker stack up -c docker-compose.yml mystack" reads just the "docker-compose.yml" file and runs Nginx in a host network mode.
* Environment variables take precedence over values in the "mysql/db.env" file. This trick allows using values from a .env file in a local deployment, and environment variables are used in production.
* A "clear_env = no" clause in "fpm.conf" file allows passing
environment variables to PHP scripts. Only variables listed in
"docker-compose.yml" are passed, so it's safe.
A single-instance PHP site is quite productive
if done right, it may handle traffic at a million daily users scale, and
is very cost-effective. Just keep in mind that a single-instance
architecture has
SPOFs, does not provide failover and adds problems for implementing the
blue-green deployment.
That's it, now I can deploy simple PHP scripts with Nginx, MySQL, TLS with ACME certificates, HTTP2 and IPv6 using a single command.
No comments:
Post a Comment