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

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.