Nginx, PHP-FPM und Let’s Encrypt per Docker-Compose aufsetzen

web-updates-kmu-wuk-webagentur-fuer-wordpress-admin-ajax-august2019

Um einen LAMDA Server einfach aufzusetzen, wie in meinem Fall auf Amazon EC2, geht es am Einfachsten mit Docker-Compose. Damit lassen sich die einzelnen Container schnell aktualisieren und System unabhängig betreiben.

Als Erstes installieren wir auf der neuen EC2 Instanz (ich verwende ein Ubuntu 18.4) gewisse Dinge:

apt update && apt upgrade
apt install -y mysql-client curl python unzip

Ich installiere alles in das /opt Verzeichnis und erstelle dazu diverse weitere Verzeichnisse die benötigt werden:

mkdir -p /opt/www/cache /root/.aws /opt/www/log/certbot /opt/www/log/nginx /opt/www/log/php /opt/www/certbot/conf /opt/www/certbot/www

Jetzt installieren wir Docker und Docker-Compose.

apt-get install -y apt-transport-https ca-certificates gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose

Als nächstes benötigen wir je nach gewünschten Diensten die Dockerfiles:

/opt/docker/php/Dockerfile:

FROM php:fpm-alpine
RUN apk update && apk add --no-cache python sudo && docker-php-ext-install mysqli &&\
 mkdir -p /var/log/php /etc/nginx/cache /root/scripts

/opt/docker/nginx/Dockerfile:

FROM nginx:alpine
RUN apk update && apk add --no-cache logrotate &&\
 mkdir -p /var/log/nginx /etc/nginx/cache /var/www/certbot

Die beiden Container müssen wir nun erstellen:

cd /opt/docker/nginx/
docker build --progress=plain -t my-nginx --force-rm -f Dockerfile .
cd /opt/docker/php/
docker build --progress=plain -t my-php --force-rm -f Dockerfile .

Nun können bereits die Webdateien in das Verzeichnis /opt/www kopiert werden.

Leider startet der Nginx nicht ohne Zertifikate. Let's Encrypt kann aber Zertifikate nur ausstellen bei lauffähigem Nginx. Daher erstellen wir einmalige Wegwerf-Self-Signed-Zertifikate für jede Domain die wir nutzen wollen:

openssl rand -writerand /root/.rnd
mkdir -p /opt/www/certbot/conf/live/FULLDOMAINNAME
openssl req -x509 -nodes -newkey rsa:1024 -days 90 -keyout '/opt/www/certbot/conf/live/FULLDOMAINNAME/privkey.pem' -out '/opt/www/certbot/conf/live/FULLDOMAINNAME/fullchain.pem' -subj '/CN=localhost'
rm /root/.rnd

Wir erstellen folgendes Script /opt/www/certbot/conf/start.sh und ersetzen FULLDOMAINNAME wieder überall

if [ ! -d "/opt/www/certbot/conf/accounts" ]; then
	echo "No Lets Encrypt Account found, wait 5s and start getting Certifications..."
	sleep 5s
	mv /etc/letsencrypt/live/FULLDOMAINNAME /etc/letsencrypt/live/FULLDOMAINNAME-tmp
	certbot certonly --webroot -w /var/www/certbot --email marketing@sintratec.com --no-eff-email -d FULLDOMAINNAME --cert-name FULLDOMAINNAME --rsa-key-size 4096 --agree-tos --force-renewal --expand --allow-subset-of-names
	if [ ! -d "/etc/letsencrypt/live/staging.sintratec.com" ]; then
	mv /etc/letsencrypt/live/FULLDOMAINNAME-tmp /etc/letsencrypt/live/FULLDOMAINNAME.com
	else
	rm -R /etc/letsencrypt/live/FULLDOMAINNAME-tmp
	fi
fi

Und geben ihm Ausführrechte:

chmod 740 /opt/www/certbot/conf/start.sh

Was nun noch fehlt, sind die Konfigurationsdateien, welche man hier für PHP und Nginx runterladen kann: etc.zip, was ins /opt Verzeichnis entpackt werden kann. Im Nginx sites/ Ordner, müssen noch die entsprechenden Domains eingetragen werden, welche ausgeliefert werden sollten (siehe FULLDOMAINNAME).

Jetzt erstellen wir noch die Datei /opt/docker-compose.yml mit folgendem Inhalt:

version: '3.2'
services:
 php:
 container_name: php
 image: my-php:latest
 restart: always
 volumes:
 - type: bind
 source: /opt/www
 target: /var/www
 bind-propagation: rshared
 - type: bind
 source: /opt/etc/php
 target: /usr/local/etc
 bind-propagation: rshared
 - type: bind
 source: /opt/www/cache
 target: /etc/nginx/cache
 bind-propagation: rshared
 - type: bind
 source: /opt/www/log/php
 target: /var/log/php
 bind-propagation: rshared
 - type: bind
 source: /opt/scripts/client
 target: /root/scripts
 bind-propagation: rshared
 - type: bind
 source: /opt/git/etc/aws
 target: /root/.aws
 bind-propagation: private
 nginx:
 container_name: nginx
 image: my-nginx:latest
 ports:
 - "80:80"
 - "443:443"
 command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
 restart: always
 depends_on:
 - php
 volumes:
 - type: bind
 source: /opt/www
 target: /var/www
 bind-propagation: rshared
 - type: bind
 source: /opt/etc/nginx/conf.d
 target: /etc/nginx/conf.d
 bind-propagation: rshared
 - type: bind
 source: /opt/etc/nginx/sites
 target: /etc/nginx/sites
 bind-propagation: rshared
 - type: bind
 source: /opt/etc/nginx/cert
 target: /etc/nginx/cert
 bind-propagation: rshared
 - type: bind
 source: /opt/etc/nginx/nginx.conf
 target: /etc/nginx/nginx.conf
 bind-propagation: rshared
 - type: bind
 source: /opt/www/cache
 target: /etc/nginx/cache
 bind-propagation: rshared
 - type: bind
 source: /opt/www/log/nginx
 target: /var/log/nginx
 bind-propagation: rshared
 - type: bind
 source: /opt/www/certbot/conf
 target: /etc/letsencrypt
 bind-propagation: rshared
 - type: bind
 source: /opt/www/certbot/www
 target: /var/www/certbot
 bind-propagation: rshared
 certbot:
 container_name: certbot
 image: certbot/certbot
 restart: always
 depends_on:
 - nginx
 entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
 volumes:
 - type: bind
 source: /opt/www/certbot/conf
 target: /etc/letsencrypt
 bind-propagation: rshared
 - type: bind
 source: /opt/www/certbot/www
 target: /var/www/certbot
 bind-propagation: rshared
 - type: bind
 source: /opt/www/log/certbot
 target: /var/log/letsencrypt
 bind-propagation: rshared

Achtung, MySQL wird nicht mitgeliefert, kann aber einfach eingebaut werden.

Das Docker Konstrukt wird mit folgendem Befehl gestartet:

docker-compose -f /opt/docker-compose.yml up --force-recreate -d ; Startet Docker Container
docker exec `docker ps -a -f name=certbot -q` /etc/letsencrypt/start.sh ; Let's Encrypt soll Zertifikate requesten
docker exec `docker ps -a -f name=nginx -q` nginx -s reload ; Neue Zertifikate im Nginx laden

oder gestoppt:

docker-compose -f /opt/docker-compose.yml down ; Docker beenden
docker ps -a | grep Exit | cut -d ' ' -f 1 | xargs sudo docker rm ; Falls Docker nicht sauber beenden, kann man sie damit löschen

Fehlerdateien sind im /opt/log der Container zu finden.