code coding computer data
TYPO3 Blog

Docker-compose Setup mit nginx reverse proxy

Wie Sie mehrere voneinander unabhängige Projekte auf einem einzelnen Server betreiben.

Auf einem einzelnen Server wollten wir mehrere von einander unabhängige Projekte mit docker-compose betreiben, die nach außen über einen reverse proxy erreichbar sind.

Bei Hinzufügen eines weiteren Projektes sollte der reverse proxy weder manuell konfiguriert werden müssen noch manuell neu gestartet werden.

Stattdessen sollte der reverse proxy das neue Projekt automatisch erkennen und dieses unter einem im Projekt konfigurierbaren vhost Namen bereitstellen.

Erreicht haben wir das durch Einsatz des docker images jwilder/nginx-proxy.

Die Verzeichnisstruktur des Servers im home-Verzeichnis des SSH-Benutzers /home/myuser sieht dabei so aus:

/frontproxy
   docker-compose.yml
   /certs
      meinedomain.de.crt
      meinedomain.de.key

/project1.meinedomain.de
   docker-compose.yml

/project2.meinedomain.de
   docker-compose.yml

...

/default.vhost
   docker-compose.yml

Die Konfiguration des reverse proxy in der Datei /home/myuser/frontproxy/docker-compose.yml sieht wie folgt aus:

version: '2'
services:
  frontproxy:
  image: jwilder/nginx-proxy:latest
  container_name: frontproxy
  restart: always
  environment:
    DEFAULT_HOST: default.vhost
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./certs:/etc/nginx/certs
    - /var/run/docker.sock:/tmp/docker.sock:ro

Der Container wird nun gestartet mittels:

cd /home/myuser/frontproxy
docker-compose up -d

Dadurch erstellt docker ein Netzwerk mit dem Namen frontproxy_default.

Nun kann man ein beliebiges neues Projekt hinzufügen mit einer geeigneten docker-compose Konfiguration, die zur Kommunikation mit dem Frontproxy folgende Direktiven beinhalten muss:

version: '2'
services:
  <someservice>:
  image: <some-image>
  restart: always
  networks:
    - frontproxy_default
    - default
  environment:
    - VIRTUAL_HOST=project1.meinedomain.de
 networks:
   frontproxy_default:
     external: true

Entscheidend ist hier das Hinzufügen des Netzwerks frontproxy_default als externes Netzwerk. Extern bedeutet hierbei, dass es nicht innerhalb dieses docker-compose setups erstellt wurde, sondern durch das docker-compose setup des frontproxys.

Auf diese Weise lassen sich beliebig viele Projekte hinzufügen. Sobald diese mit docker-compose up -d gestartet wurden, erkennt der frontproxy anhand der Umgebungsvariable VIRTUAL_HOST, dass er Anfragen an diesen virtual host zu diesem Docker-Container weiterleiten muss.

Als Port wird automatisch der Port verwendet, der durch das Image des Projekts im Dockerfile via EXPOSE angegeben wurde. Mittels einer weiteren Umgebungsvariable VIRTUAL_PORT kann man diesen auch spezifizieren.

Interessant an diesem Setup ist, dass sich beliebig viele Projekte hinzufügen lassen, die alle auf dem selben Port (i.d.R. Port 80 für HTTP) laufen. Man muss also nicht manuell dafür sorgen, dass es keine Port-Überschneidungen gibt.

Wichtig ist, dass bei der Inbetriebnahme dieses Setups zuerst der frontproxy gestartet wird. Denn dieser erstellt das Netzwerk frontproxy_default, das alle weiteren Projekte benötigen.

Bei einem Neustart des Servers wird diese Reihenfolge beibehalten, so dass docker automatisch durch das „restart:always“ zuerst den frontproxy startet und danach die Projekte.

SSL-Zertifikate

Wenn man die Projekte als Subdomains von meinedomain.de anlegt, dann kann man mit einem einzigen SSL-Wildcard-Zertifikat für *.meinedomain.de alle Projekte auch gleich per SSL ausliefern lassen. 

Möchte man unterschiedliche Domainnamen verwenden, so kann man im Verzeichnis /home/myuser/frontproxy/certs/ für jeden Domainnamen ein entsprechend benanntes Zertifikat und den zugehörigen Key ablegen.

Im Nachtrag unten ist beschrieben, wie man SSL-Zertifikate über Let’s Encrypt automatisch verwalten lässt.

Default vhost

Die Umgebungsvariable DEFAULT_HOST in der Konfiguration des Frontproxys gibt an, welcher Inhalt ausgeliefert werden soll, wenn der Frontproxy mit einem Hostnamen aufgerufen wird, für den kein registrierter vhost existiert. Macht man hier keine Angabe, so würde der Inhalt des ersten registrierten vhosts ausgegeben, was nicht immer optimal ist. 

Insbesondere kann das zum Problem werden, wenn mal ein vhost wegen eines Ausfalls des betreffenden Docker Containers nicht erreichbar ist. Wir haben daher als default.vhost einen docker Container mit einem einfachen Webserver eingerichtet, der einfach eine statische HTML-Seite ausliefert, die darauf hinweist, dass der vhost entweder unbekannt oder aktuell nicht erreichbar ist.

Nachtrag: SSL-Zertifikate über Let’s Encrypt verwalten

Wenn man offizielle Domains verwendet, die auch per DNS im öffentlichen Netz aufgelöst werden können, kann man die SSL-Zertifikatsgenerierung auch komplett automatisch (und kostenlos!) mittels Let’s Encrypt erledigen lassen.

Dazu gibt es das docker-image docker-letsencrypt-nginx-proxy-companion.

Die docker-compose.yml Datei muss dann um diesen Service erweitert werden:

version: '2'
 
services:
  frontproxy:
    restart: always
    image: jwilder/nginx-proxy
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/tmp/docker.sock:ro"
      - "certs-volume:/etc/nginx/certs:ro"
      - "/etc/nginx/vhost.d"
      - "/usr/share/nginx/html"
  nginx-letsencrypt-companion:
    restart: always
    image: jrcs/letsencrypt-nginx-proxy-companion
    volumes:
      - "certs-volume:/etc/nginx/certs"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    volumes_from:
      - "frontproxy"
volumes:
  certs-volume:

Ein Projekt, das diesen frontproxy zusammen mit der SSL-Zertifikatsverwaltung nutzen soll, braucht dann folgende Angaben in seiner docker-compose.yml:

version: '2'
 
services:
  project1:
    restart: always
    build: .
    networks:
      - frontproxy_default
      - default
    environment:
      - VIRTUAL_HOST=project1.example.com
      - LETSENCRYPT_HOST=project1.example.com
      - LETSENCRYPT_EMAIL=webmaster@example.com
networks:
  frontproxy_default:
    external: true

Entscheidend sind hier die Umgebungsvariablen LETSENCRYPT_HOST und LETSENCRYPT_EMAIL. Erstere definiert den Domainnamen, für den der Let’s Encrypt Service das SSL-Zertifkat erstellen soll und letztere den Kontakt (E-Mail) für das Zertifikat.

Die regelmäßige, rechtzeitige Erneuerung des Zertifikats erfolgt voll automatisch.