Blog

Einrichten eines Servers für die cloudbasierte Webentwicklung (2/4)

Veröffentlicht vor einem Monat

authelia
code-server
docker
grafana
traefik
Illustration zum Blogeintrag

Überblick über die Serie

Server einrichten, Docker und docker-compose installieren

Auf die Grundeinrichtung des Servers möchte ich an dieser Stelle nicht besonders eingehen, es gibt dazu schon genug Anleitungen im Internet, zum Beispiel diese hier von DigitalOcean:

https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-20-04-de

Entscheidend ist nur, dass man sich als Nutzer mit Aministrator- Rechten mit dem Server verbinden kann (z.B. über SSH).

Außerdem müssen noch docker und docker-compose auf dem Server installiert werden. Hier verweise ich auch wieder auf die guten Tutorials von DigitalOcean:

Server mit Github authentifizieren

Da wir später die erstellten Docker-Images in der privaten Github Container Registry bereitstellen, müssen wir unseren Server noch mit Github authentifizieren, um dann auch die Images herunterladen zu können.

Dazu benötigen wir zunächst ein “Personal Access Token” von Github. Dieses kann unter “Settings” → “Developer Settings” → “Personal Access Tokens” erstellt werden.

Personal Access Token auf Github

Unter “Note” kann man angeben, wofür der Token genutzt wird, so dass man den Token später wieder identifizieren kann, wenn man mehrere Personal Access Tokens erstellt hat.

Der Einfachheit halber setzen wir kein Ablaufdatum für den Token. Für eine verbesserte Sicherheit sollte man hier einen nicht allzu langen Zeitraum angeben. Dann müsste man diese Schritte aber regelmäßig wiederholen und da der Token nur für diesen einen Zweck genutzt wird, sollte diese Einstellung in Ordnung sein.

Außerdem brauchen wir die folgenden Scopes für den Token:

  • write:packages
  • read:packages
  • delete:packages
  • repo (nur, wenn das Repository privat ist)

Den neuen Token müssen wir entweder sofort nutzen oder an einem sicheren Ort (am besten einem Passwort- Manager) speichern, da er nur dieses eine Mal nach dem Erstellen auf Github angezeigt wird. Wenn man den Token nicht gespeichert hat und ihn aber wieder braucht, muss ein neuer Token erstellt werden.

Mit dem Token können wir dann auf dem Server den folgenden Befehl ausführen um die Docker- Instanz mit Github zu authentifizieren:

docker login ghcr.io --username GITHUB-USERNAME

Bei der Abfrage des Passworts geben wir den soeben erstellen Personal Access Token ein. Fortan kann unser Server also unsere privaten Docker- Images von Github herunterladen.

DNS- Einstellungen

Als letzte Voraussetzung wird eine Domain benötigt, bei der die DNS- Einstellungen bearbeitet werden können. Hier erstellen wir einen neuen A- Record, der auf die IP- Adresse unseres Servers verweist.

Host: *.server
Type: A-Record
Destination: IP-ADRESSE

Mit diesem Eintrag werden alle Subdomains unter *.server.MY-DOMAIN an den Server weitergeleitet. Verfügt der Server auch über eine IPv6- Adresse kann man noch einen AAAA- Record nach dem gleichen Schema anlegen.

Github-Repository und Ordnerstruktur

Zuallererst erstellen wir ein neues Repository auf Github, in dem wir unsere Konfigurationsdateien speichern und das auch die Build- Prozesse der Docker- Container verwaltet.

Nachdem das neue Repository auf die lokale Maschine geklont wurde, können wir schon einmal die folgenden Ordner anlegen:

.
├── .github
│   └── workflows
├── authelia
│   ├── config
├── code-server
│   ├── config
│   └── fonts
├── prometheus
│   └── config
└── traefik

Authentifizierung mit Authelia

Unsere Instanz von code-server sowie die anderen Apps schützen wir mit Hilfe von Authelia vor unberechtigtem Zugriff. Authelia ist ein docker-basierter Dienst, der 2- Faktor- Authentifizierung als Middleware für einen Reverse Proxy (in unserem Fall Traefik) bereitstellt.

Für die Konfiguration von Authelia werden zwei yaml- Dateien benötigt: eine für die eigentliche Konfiguration und eine zweite für die Bereitstellung der Nutzerdaten. Beide Dateien werden wir wie im ersten Teil beschrieben direkt in den Container integrieren.

Konfiguration von Authelia

Wir erstellen also eine Datei mit dem Namen configuration.yml im Ordner ./authelica/config mit dem folgenden Inhalt:

# ./authelia/config/configuration.yml
---
###############################################################
#                   Authelia configuration                    #
###############################################################

default_redirection_url: https://code.server.<MY-DOMAIN>
theme: dark

server:
  host: 0.0.0.0
  port: 9091

log:
  level: warn

totp:
  issuer: authelia.com

authentication_backend:
  file:
    path: /config/users_database.yml

access_control:
  default_policy: deny
  rules:
    # Rules applied to everyone
    - domain:
        - auth.server.MY-DOMAIN.de
      policy: bypass

    - domain:
        - proxy.server.<MY-DOMAIN>
        - grafana.server.<MY-DOMAIN>
        - vscode.server.<MY-DOMAIN>
        - port-3000.server.<MY-DOMAIN>
        - port-4200.server.<MY-DOMAIN>
        - port-4400.server.<MY-DOMAIN>
      policy: two_factor

session:
  name: authelia_session
  expiration: 86400 # 1 day
  inactivity: 3600 # 1 hour
  domain: server.MY_DOMAIN.de # Should match whatever your root protected domain is

  redis:
    host: redis
    port: 6379

regulation:
  max_retries: 3
  find_time: 120
  ban_time: 300

storage:
  local:
    path: /database/db.sqlite3

notifier:
  smtp:
    username: me@example.com
    host: <SMTP-SERVER>
    port: 587
    sender: me@example.com

Hier ist natürlich überall <MY-DOMAIN> durch die Domain zu ersetzen, die weiter oben konfiguriert wurde.

Schauen wir uns nun also die einzelnen Teile der Konfigurationsdatei an:

  • default_redirection_url: Hierher wird man nach erfolgreichem Login weitergeleitet
  • server: Dieses Statement legt fest, auf welchem Port Authelia auf Anfragen wartet. Diesen Post müssen wir später für den Docker-Container freigeben.
  • authentication_backend: Hier wird der Speicherort der Nutzerdaten festgelegt. Diese Datei werden wir im nächsten Schritt erstellen.
  • access_control: Dies ist der alles entscheidende Abschnitt, weil hier festgelegt wird, welche Domains von Authelia geschützt werden. Die default_policy setzen wir dabei auf deny, damit alle eingehenden Anfragen abgelehnt werden, die nicht einer der im folgenden definierten Subdomains entsprechen. Für die Domain [auth.server.MY-DOMAIN.de](http://auth.server.MY-DOMAIN.de) setzen wir die die policy auf bypass, damit der Anmeldebildschirm von Authelia erreicht werden kann, ohne vorher angemeldet zu sein. Im nächsten Block werden alle benötigten Domains aufgelistet und mit der policytwo_factor versehen. Somit können diese Domains nur von mittels 2- Faktor- Authentifizierung angemeldeten Nutzern aufgerufen werden. Es sind auch noch feinere Abstufungen möglich (z.B. Nutzergruppen), für unsere Zwecke reicht es so aber aus. Weitere Informationen findet man in der Dokumentation von Authelia.
  • session: Hier wird festgelegt, wie lange ein Nutzer ohne Aktivität angemeldet bleibt. Die Sitzung wird dabei bei jedem Aufruf einer der geschützten Domains wieder um den angegebenen Wert verlängert. Zusätzlich nutzen wir noch eine Redis- Datenbank als Cache für die Daten der Sitzung zur Optimierung der Zugriffszeiten. Schließlich müssen die Sitzungsdaten für jede Anfrage abgerufen werden. Diese Redis- Instanz wird später in einem eigenen Container bereitgestellt.
  • storage: Für die persistente Speicherung der Nutzer- und Sitzungsdaten nehmen wir der Einfachheit halber eine einfache SQLite- Datei, die mittels eines Docker- Volumes auf der Festplatte des Servers gespeichert wird. Alle Daten werden natürlich verschlüsselt in der SQLite- Datei abgelegt. Als Schlüssel wird später noch eine Environment- Variable auf Github definiert.
  • notifier: Nach der erstmaligen Anmeldung bei Authelia wird eine E-Mail an die in user_database.yml festgelegte Adresse verschickt. Diese E-Mail enthält einen Link zu einem QR-Code, mit dem sich eine App wie zum Beispiel der “Google Authenticator” mit Authelia verbinden lässt. Damit diese E-Mail versendet werden kann, muss in diesem Abschnitt ein SMTP- Server definiert werden. Das Passwort des Servers wird über eine Environment Variable in Github beim Build-Prozess in den Container übertragen. Sollte kein SMTP- Server zur Verfügung stehen, kann man sich die Benachrichtigung auch auf der Festplatte des Servers speichern lassen. Weitere Infos dazu gibt es hier.

Nutzerdatenbank

Als nächsten Schritt legen wir die zweite YAML- Datei (users_database.yml) mit den Nutzerdaten im selben Ordner an:

# ./authelia/config/users_database.yml
---
###############################################################
#                         Users Database                     #
###############################################################

# List of users
users:
  <USERNAME>:
    displayname: <JOHN DOE>
    password: <MY-PASSWORD>
    email: <ME@EMAIL.COM>

In dieser Datei sind natürlich wieder die persönlichen Daten einzutragen. Dabei empfiehlt es sich, für den Benutzernamen die E-Mail- Adresse zu verwenden. Dies ist aber nicht notwendig.

Das Passwort des Nutzers muss hier als Password- Hash abgelegt werden. Ein solches Passwort- Hash kann folgendermaßen erzeugt werden:

$ docker run authelia/authelia:latest authelia hash-password -- '<PASSWORD>'
Password hash: $argon2id$v=19$m=65536$3oc26byQuSkQqksq$zM1QiTvVPrMfV6BVLs2t4gM+af5IN7euO0VB6+Q8ZFs

Anschließend muss einfach der komplette Text beginnend bei $argon… in die users_database.yml an die Stelle von <MY-PASSWORD> kopiert werden.

Dockerfile

Die für die Erstellung unseres eigenen Authelia- Containers notwendigen Schritte werden im entsprechenden Dockerfile festgelegt:

# ./authelia/Dockerfile

# base image
FROM authelia/authelia:4

# copy config files into container
RUN mkdir -p /config
COPY ./config/configuration.yml /config/configuration.yml
COPY ./config/users_database.yml /config/users_database.yml

# add secrets to container
RUN mkdir -p /secrets
RUN --mount=type=secret,id=AUTHELIA_JWT_SECRET cat /run/secrets/AUTHELIA_JWT_SECRET > /secrets/jwt
RUN --mount=type=secret,id=AUTHELIA_SESSION_SECRET cat /run/secrets/AUTHELIA_SESSION_SECRET > /secrets/session
RUN --mount=type=secret,id=AUTHELIA_STORAGE_ENCRYPTION_KEY cat /run/secrets/AUTHELIA_STORAGE_ENCRYPTION_KEY > /secrets/encryption
RUN --mount=type=secret,id=AUTHELIA_NOTIFIER_SMTP_PASSWORD cat /run/secrets/AUTHELIA_NOTIFIER_SMTP_PASSWORD > /secrets/smtp

Als Ausgangspunkt wird das offizielle Authelia- Image von Dockerhub genutzt. Anstatt latest wird die aktuelle Versionsnummer angegeben, um zu verhindern, dass durch breaking changes einer neuen Version von Authelia das Setup nicht mehr funktioniert.

Im nächsten Schritt erstellen wir einen Ordner für die Konfigurationsdateien im Container und kopieren diese Dateien in diesen neuen Ordner.

Zum Abschluss werden noch die Secrets des Github Repositories, die Authelia betreffen, in einem neuen Ordner im Container abgelegt.

Secrets für Github Action definieren

Wie jetzt schon mehrfach angesprochen, benötigt Authelia ein paar vertrauliche Daten, die nicht im Github Repo abgelegt werden sollten. Diese sind:

  • AUTHELIA_JWT_SECRET: Eine Zeichenkette aus Buchstaben und Zahlen, mit der die Web Tokens für die Identifikation von angemeldeten Benutzern verschlüsselt werden. Die Zeichenkette kann frei gewählt werden, es wird jedoch eine Länge von mindestens 64 Zeichen empfohlen.
  • AUTHELIA_SESSION_SECRET: Wird für die Verschlüsselung der Sitzungsdaten in der Redis- Datenbank genutzt. Sollte auch mindestens 64 Zeichen lang sein.
  • AUTHELIA_STORAGE_ENCRYPTION_KEY: Dieser Key wird für die Verschlüsselung der Daten in der SQLite- Datei verwendet. Auch hier wird eine Länge von Mindestens 64 Zeichen empfohlen.
  • AUTHELIA_NOTIFIER_SMTP_PASSWORD: Zusätzlich zu den verschiedenen Schlüsseln muss auch noch das Passwort des SMTP- Servers angegeben werden, so dass Authelia die Benachrichtigungsmails über diesen Server versenden kann.

Zum Anlegen dieser Secrets öffnet man die Einstellungen des Repos und wählt dann unter “Secrets” → “Actions” aus. Jetzt legt man für jedes Secret einen neuen Eintrag mit dem exakt gleichen Namen und dem entsprechenden Wert an.

Github Action

Mit Github Actions können bei verschiedenen Vorgängen im Repo selbst definierte Schritte ausgeführt werden. Hier nutzen wir eine Github Action, um das Docker- Image von Authelia jedes Mal neu zu erstellen, wenn Änderungen an der Konfiguration vorgenommen werden.

Github Actions werden als YAML- Dateien im Ordner .github/workflows definiert:

# ./.github/workflows/docker-build-authelia.yml

name: build custom docker image for authelia

on:
  push:
    branches:
      - 'main'
    paths:
      - 'authelia/**'

jobs:
  build-authelia:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Set up Docker BuildX
        uses: docker/setup-buildx-action@v1

      - name: login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Push the custom Authelia Image
        uses: docker/build-push-action@v2
        with:
          context: ./authelia
          push: true
          tags: ghcr.io/<GITHUB_USERNAME>/authelia:latest
          secrets: |
            "AUTHELIA_JWT_SECRET=${{ secrets.AUTHELIA_JWT_SECRET }}"
            "AUTHELIA_SESSION_SECRET=${{ secrets.AUTHELIA_SESSION_SECRET }}"
            "AUTHELIA_STORAGE_ENCRYPTION_KEY=${{ secrets.AUTHELIA_STORAGE_ENCRYPTION_KEY }}"
            "AUTHELIA_NOTIFIER_SMTP_PASSWORD=${{ secrets.AUTHELIA_NOTIFIER_SMTP_PASSWORD }}"

Zuerst vergeben wir einen beliebigen Namen für die Action.

Mit der on- Direktive wird festgelegt, welche Vorgänge die Action starten. In unserem Fall wollen wir ein neues Docker- Image erstellen, wenn Dateien im Authelia- Ordner auf dem Branch main geändert wurden.

Unter jobs wird das Betriebssystem sowie die einzelnen Schritte festgelegt, die abgearbeitet werden sollen:

  • Als erstes wird das gesamte Repo auf die Maschine kopiert.
  • Im nächsten Schritt wird BuildX initialisiert, ein Plugin für erweiterte Funktionen beim Erstellen von Docker Images.
  • Dann müssen wir uns bei unserer privaten Container Registry anmelden. Github stellt jedem Account eine solche Registry mit begrenztem Funktionsumfang kostenlos zur Verfügung. Für unsere Zwecke reicht dies vollkommen aus.
  • Der letzte Schritt erstellt dann tatsächlich das Docker- Image für Authelia und lädt dieses in die private Container Registry hoch. Dazu wird mit context der Ordner innerhalt des Repos angegeben, in dem das Dockerfile liegt. Der tag legt fest, wie das Image innerhalb der Container Registry heißen wird. Später können wir das Image mit diesem Namen auf unseren Server herunterladen. Hier ist natürlich GITHUB_USERNAME durch den eigenen Namen auf Github zu ersetzen. Mit der secrets- Direktive werden die oben erstellen Github Secrets an den Build- Prozess weitergereicht.

Starten des Containers mit docker-compose

Da der fertige Stack später doch über einige Container verfügt, wird das Starten jedes einzelnen Containers sehr aufwändig. Daher werden wir docker-compose nutzen, um alle Container auf einmal zu starten.

Die Konfiguration der verschiedenen Container für docker-compose erfolgt ebenfalls in einer Datei docker-compose.yml, die im Hauptordner des Repos liegt.

Zunächst erstellen wir die Einträge für die Authelia- Instanz:

# ./docker-compose.yml
---
####################################################################################################
# Cloudbased Webdevelopment Server                                                               #
####################################################################################################

version: '3.3'

networks:
  proxy:
    driver: bridge
  authelia:
    driver: bridge

volumes:
  authelia-data:
    driver: local
  redis-data:
    driver: local

services:
  ################################
  # Authentication with Authelia #
  ################################
  authelia:
    image: ghcr.io/<GITHUB-USERNAME>/authelia:latest
    container_name: authelia
    volumes:
      - authelia-data:/database
    networks:
      - proxy
      - authelia
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.authelia.rule=Host(`auth.server.<MY-DOMAIN>`)'
      - 'traefik.http.routers.authelia.entrypoints=https'
      - 'traefik.http.routers.authelia.tls=true'
      - 'traefik.http.routers.authelia.tls.certresolver=letsencrypt'
      - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.server.MY-DOMAIN.de' # yamllint disable-line rule:line-length
      - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
      - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email' # yamllint disable-line rule:line-length
      - 'traefik.docker.network=proxy'
      - 'com.centurylinklabs.watchtower.enable=true'
    expose:
      - 9091
    restart: unless-stopped
    healthcheck:
      disable: true
    environment:
      - AUTHELIA_JWT_SECRET_FILE=/secrets/jwt
      - AUTHELIA_SESSION_SECRET_FILE=/secrets/session
      - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/encryption
      - AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/smtp
      - TZ=Europe/Berlin

  redis:
    image: redis:alpine
    container_name: redis
    volumes:
      - redis-data:/data
    networks:
      - authelia
    expose:
      - 6379
    restart: unless-stopped
    environment:
      - TZ=Europe/Berlin

Auch in der docker-compose.yml müssen wieder GITHUB-USERNAME und MY-DOMAIN durch die eigenen Daten ersetzt werden.

In den einzelnen Abschnitten nehmen wir folgende Einstellungen für unseren Stack vor:

  • networks: Damit die einzelnen Docker-Container Daten untereinander austauschen können, müssen sie sich im selben Netzwerk befinden. Da wir über einen Reverse Proxy verschiedene Container über das Internet erreichbar machen wollen, definieren wir ein Netzwerk mit dem Namen proxy. Die Redis- Datenbank, in der Authelia Sitzungsdaten zwischenspeichert, muss aber nicht vom Reverse Proxy erreichbar sein, daher wird noch ein zweites Netzwerk authelia definiert, über das unsere Authelia- Instanz auf die Datenbank zugreifen kann.
  • volumes: Es ist von Vorteil, wenn gewisse Daten von Authelia (SQLite- Datei) sowie die Redis- Datenbank dauerhaft gespeichert werden. Daher erstellen wir je ein Volume für beide Container.
  • services: In diesem Abschnitt werden all Container definiert, aus denen der Stack bestehen soll. Das sind in unserem Fall zunächst Authelia und Redis.
    • Das Image für den Authelia- Container wird von der privaten Github Container Registry heruntergeladen. Dann wird das oben definierte Volume in den Container eingehängt und der Container beiden Netzwerken zugeordnet. Über die labels wird der noch einzurichtende Reverse Proxy Traefik gesteuert. Zuerst wird die Seite zum Anmelden in Authelia über https unter der angegebenen Seite verfügbar gemacht. Und als nächster Schritt wird dann das Verhalten von Authelia als Traefik Middleware konfiguriert. Das letzte Label aktiviert den Dienst watchtower für diesen Container, der dafür sorgt, dass neue Versionen des Images automatisch heruntergeladen und der Container neu gestartet wird. Als nächstes wird der Port 9091, auf dem Authelia auf Anfragen wartet, für das Netzwerk verfügbar gemacht. Die Umgebungsvariablen geben an, wo im Container die Secrets liegen, die während des Build- Prozesses mit Github Actions im Container abgelegt wurden.
    • Die Konfiguration des Redis- Container entspricht dem Standard. Die einzige Besonderheit ist die Zuordnung zum Netzwerk authelia.

Und damit ist der erste Service für unseren Server erstellt. So allein kann Authelia aber noch überhaupt nichts machen, da eingehende Anfragen bisher nicht an den Authelia- Container weitergeleitet werden.

Traefik Reverse Proxy

Das Weiterleiten der eingehenden Anfragen an den richtigen Container übernimmt ein Reverse Proxy, in diesem Fall nutzen wir Traefik.

Es gibt verschiedene Wege, Traefik zu konfigurieren. Wir nutzen hier eine Kombination aus Labels und Kommandozeilen- Optionen, die beide in der docker-compose- Datei gespeichert werden. Eine eigene Konfigurationsdatei kann daher entfallen.

Dockerfile

Es gibt jedoch trotzdem noch eine Sache, um die wir das Standard- Image von Traefik erweitern müssen: ein Speicherort für die Schlüssel für die Verschlüsselung des Netzwerk- Traffics mit https.

# ./traefik/Dockerfile

# base image
FROM traefik:2.4

# create file to store letsancrypt keys
RUN mkdir -p /etc/traefik
RUN touch /etc/traefik/acme.json
RUN chmod 600 /etc/traefik/acme.json

Hierzu legen wir einfach während des Build- Prozesses eine neue leere Datei acme.json im Ordner /etc/traefik im Container an. Anschließend müssen noch die Zugangsrechte so angepasst werden, dass nur der Root- Benutzer die Datei lesen und schreiben kann. Später werden wir dann ein Docker- Volume definieren, mit dem diese Datei dauerhaft auf dem Server gespeichert wird.

Github Action

Weil ja das Standard- Image von Traefik verändert wird, muss auch dieses Image mit einer Github Action neu erstellt werden.

# ./.github/workflows/docker-build-traefik.yml

name: build custom docker image for traefik

on:
  push:
    branches:
      - 'main'
    paths:
      - 'traefik/**'

jobs:
  build-traefik:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Set up Docker BuildX
        uses: docker/setup-buildx-action@v1

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Push the custom Traefik Image
        uses: docker/build-push-action@v2
        with:
          context: ./traefik
          push: true
          tags: ghcr.io/<GITHUB-USERNAME>/traefik:latest

Die Schritte hier entsprechen denen für das Authelia- Image. Es wurde nur der Speicherort des Dockerfile sowie der Name des fertigen Images in der Github Container Registry abgeändert. Außerdem gibt es für dieses Image keine Umgebungsvariablen, die in das Image übergeben werden müssen.

Ergänzung von docker-compose.yml

Um Traefik gemeinsam mit den anderen Containern des Stacks zu starten, muss noch die docker-compose.yml erweitert werden.

# ./docker-compose.yml

volumes:
  # ...
  traefik-data:
    driver: local

Als erstes wird ein weiteres Volume erstellt, in dem Traefik die lokalen Daten ablegen und für einen Neustart speicher kann.

Im Bereich services werden die folgenden Konfigurationen ergänzt:

# ./docker-compose.yml

services:
  # ...
  #########################
  # Traefik Reverse Proxy #
  #########################
  traefik:
    image: ghcr.io/<GITHUB-USERNAME>/traefik:latest
    container_name: traefik
    volumes:
      - traefik-data:/etc/traefik
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - proxy
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.api.rule=Host(`proxy.server.<MY-DOMAIN>`)'
      - 'traefik.http.routers.api.entrypoints=https'
      - 'traefik.http.routers.api.service=api@internal'
      - 'traefik.http.routers.api.tls=true'
      - 'traefik.http.routers.api.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.api.middlewares=authelia@docker'
      - 'traefik.docker.network=proxy'
      - 'com.centurylinklabs.watchtower.enable=true'
    ports:
      - 80:80
      - 443:443
    command:
      - '--api'
      - '--providers.docker=true'
      - '--providers.docker.exposedByDefault=false'
      - '--entrypoints.http=true'
      - '--entrypoints.http.address=:80'
      - '--entrypoints.http.http.redirections.entrypoint.to=https'
      - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
      - '--entrypoints.https=true'
      - '--entrypoints.https.address=:443'
      - '--certificatesResolvers.letsencrypt.acme.email=<ME@EMAIL.COM>'
      - '--certificatesResolvers.letsencrypt.acme.storage=/etc/traefik/acme.json'
      - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
      - '--log=true'
      - '--log.level=ERROR'
    restart: unless-stopped
    depends_on:
      - 'authelia'

FÜr den Traefik- Container werden zwei Volumes benötigt: traefik-data zum Ablegen der über letsencrypt bezogenen SSL- Zertifikate und /var/run/docker.sock um dem Container Zugriff auf die Docker- Instanz des Servers zu gewähren, damit Traefik andere Container finden kann, für die eine Route erstellt werden muss.

Die Labels gleichen denen des Authelia- Containers und sind notwendig, um die Weboberfläche des Traefik- Containers in einem Webbrowser aufrufen zu können.

Die gesamte Konfiguration des Reverse Proxy erfolgt über die Einträge in command. Der wichtigste Teil hierbei ist, dass eingehende http- Anfragen auf Port 80 auf Port 443 umgeleitet werden, wo eine SSL- Verschlüsselung erforderlich ist. Die für die Verschlüsselung benötigten Zertifikate können kostenlos über letsencrypt bezogen werden. Dazu muss allerings noch die eigene E-Mail- Adresse angegeben werden (statt ME@EMAIL.COM).

Watchtower

In Teil 1 habe ich bei den Anforderungen beschrieben, dass Änderungen möglichst einfach am Server vorgenommen werden können sollen. Dazu haben wir ja die Konfigurationsdateien jeweils direkt mittels Github Actions in die Images integriert.

Jetzt müssen wir aber auch noch sicherstellen, dass diese nach Änderungen neu erstellten Images auch auf den Server geladen und die entsprechenden Container neu gestartet werden. Hierzu kommt Watchtower zum Einsatz.

Wir fügen also die folgenden Zeilen noch am Ende der docker-compose.yml ein:

# ./docker-compose.yml

services:
  # ...
  ##############
  # Watchtower #
  ##############
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ~/.docker/config.json:/config.json
    environment:
      TZ: Europe/Berlin
      WATCHTOWER_CLEANUP: 'true'
      WATCHTOWER_LABEL_ENABLE: 'true'
      WATCHTOWER_SCHEDULE: '0 */15 * * * *'

Mit dieser Konfiguration wird Watchtower immer in Intervallen von 15 Minuten überprüfen, ob für einen Container mit dem Label com.centurylinklabs.watchtower.enable=true ein neues Image vorhanden ist, dieses gegebenenfalls herunterladen und den Container neu starten.

Um diese Aufgabe erfüllen zu können, braucht Watchtower Zugriff auf den Docker- Socket und die Zugangsdaten zur privaten Github Container Registry. Diese Daten machen wir mit den beiden Volumes im Container verfügbar.

Wie geht es weiter?

Nun sind endlich die Vorarbeiten erledigt und die Infrastruktur der Servers steht. Im nächsten Teil können wir uns dann um den code-server kümmern.

Teil 3: code-server