en / de
AI
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

CI/CD Pipeline mit GitHub und Dokku

by Simon Haerdi 16. Dezember 2024· 7 Min. lesen

Einleitung

Wie erstelle ich eine CI/CD-Pipeline mit GitHub und Dokku für eine kleine Website?

Viele Software-Entwickler üben ihren Beruf nicht nur im Büro, sondern auch in ihrer Freizeit aus. Da noch ein Hobby-Projekt, dort noch eine Kleinigkeit, um einem Bekannten zu helfen. So war es auch bei mir.
Die Website, welche als Online-Präsenz und Verwaltungstool unseres Vereins dient, ist in Eigenregie und als «Learning-by-doing»-Projekt entstanden. Wie das so ist, waren auch bei diesem Projekt zu Beginn nur funktionelle Ziele vorhanden. Die qualitativen Ansprüche standen für die Kunden, in diesem Fall die Vereinsmitglieder, nicht im Vordergrund. Die Konsequenzen von so einer suboptimalen Prioritätensetzung erleben wir tagtäglich in Form von schlechter Code-Qualität, mangelnden Tests und Verzögerungen bei der Auslieferung.

Wenigstens bei diesem selbst-gemanagtem Projekt wollte ich das besser machen und habe folgende Ziele aufgestellt:

Für grössere Web-Projekte sind die gelisteten Kriterien (und noch viele mehr) Standard. Für eine kleine Vereinswebsite hingegen stellt sich die Frage, wie diese umgesetzt werden soll. Teure Technologien mit Abonnement-Gebühren wie Azure fallen aus Kostengründen weg.

Ich habe mich für eine Kombination aus GitHub und Dokku entschieden. Im Verlauf dieses Blogs werde ich zeigen, wie damit eine CI/CD-Pipeline für kleinere Projekte umgesetzt werden kann.

Technologien

Github

Die Platform GitHub dürfte wohl jedem Leser bekannt sein. Mittlerweile ist sie weit über das blosse Hosting von Git-Repositories hinausgewachsen. Für dieses Projekt werden aber bloss die beiden Funktionalitäten

gebraucht. Dabei profitiere ich davon, dass auf GitHub jeder Benutzer eine gewisse Anzahl an Rechenleistung, «execution minutes» genannt, zur Verfügung gestellt bekommt. Damit ist es möglich, die CI/CD-Tasks direkt auf GitHub ausführen zu lassen.

Dokku

Dokku bezeichnet sich selbst als «open source PAAS alternative to Heroku». Das Framework ermöglicht das automatisierte Bauen und Bereitstellen von Web-Applikationen. Dazu stellt es sich selber als «remote Git repository» zur Verfügung und erstellt bei jedem push eine neue Version des Projekt-Docker-Images. Dieser wird dann gleich instanziiert und ersetzt den alten Container.

Dokku ist Open Source und kann auf eigenen oder gemieteten Servern gehostet werden. Damit eignet es sich ideal für kleine Webprojekte. Das Einrichten auf einem Server ist sehr einfach und geschieht mit dem bereitgestellten Skript quasi vollautomatisch.

Aufbau

Git Branching Modell

Der geforderte Aufbau mit der Staging- und Production-Umgebung lässt sich gut mit einem Git-Branching Model (ähnlich zu Git-Flow) abbilden. Dabei entsprechen die beiden Umgebungen jeweils ihrem zugewiesenen Branch; release für production und develop für staging.

Git branching Strategie

Git branching Strategie

Der Workflow ist dabei wie gewohnt. Für ein neues Feature wird ein eigener Feature-Branch aus dem Develop-Branch erstellt. Anschliessend wird das Feature dort implementiert und sobald es fertig ist, wieder zurück in den Develop-Branch gemerged. Für ein neues Release wird der aktuelle Develop-Branch in den Release-Branch gemerged.

Das Mergen im Develop- und Release-Branch wird dabei ausschliesslich per Pull Request auf GitHub ausgeführt. Für bezahlende GitHub-User könnte das mit geschützten Branches erzwungen werden. Alle anderen müssen hier Geld mit Selbstdisziplin ersetzen.

Dokku Konfiguration

Dokku kann auf einem Server eine beliebige Anzahl Websites hosten. Im Dokku-Glossar werden diese «Apps» genannt. Dabei gibt es für jede App ein eigenes Git Repository und jede App hat ihre eigene Konfiguration in Form von Umgebungsvariablen.

Das eigentliche Setup unterscheidet sich je nachdem, welche Technologie für die Website verwendet wird. Es stehen auf der Dokku Seite, und natürlich im World-Wide-Web, jede Menge Tutorials für alle möglichen Kombinationen von Backend- und Frontend-Technologien zur Verfügung.

Für unsere Zwecke werden zwei Apps angelegt: Die eigentliche Website als Produktions-Umgebung und eine Kopie dieser Website als Staging-Umgebung. Beide sind komplett unabhängig voneinander und werden per Konfiguration so eingerichtet, dass sie über unterschiedliche URLs erreichbar sind.

Folgendes ist dabei zu beachten: Das initiale Setup wird durch den Befehl «dokku apps:clone» sehr vereinfacht. Rein technisch ist aber der Zusammenhang von Staging-Umgebung und Production-Umgebung derselben App auf dem Dokku-Server nicht ersichtlich. Der Prozess eines Pre-Release und anschliessendem Release wird mit GitHub Actions umgesetzt.

GitHub Actions

Die Bedingung, dass der Develop-Branch und der Release-Branch ausschliesslich mit Pull Requests auf GitHub verändert werden, ermöglicht die Kontrolle des CI/CD-Prozesses mit GitHub Actions. Dafür werden für dieses Projekt drei verschiedene Workflows angelegt.

Tests

Dieser Workflow führt die Unit- und die Integrationstest aus. Er wird bei jedem Commit in jedem Branch ausgeführt. Das Programmieren dieser Tests ist natürlich nach wie vor Aufgabe des Entwicklers. Das Erzwingen der Ausführung hilft aber beim frühen Erkennen von Regressions-Fehlern und sorgt für etwas bessere Selbstdisziplin. Dabei gilt: «Wenn ich schon extra eine Pipeline dafür aufgesetzt habe, möchte ich auch, dass Tests vorhanden sind und schreibe auch welche.»

Da ich für die Website Django und Python verwende, werden die Unit- und Integrationstests ebenfalls mit Python ausgeführt. Die Konfiguration des YAML-Files ist Standard für das Ausführen von PyTest, wobei noch ein paar Umgebungsvariablen für die Konfiguration von Django gesetzt werden:

name: "Run Tests"

on:
  pull_request:
    types:
      - opened
      - edited
      - synchronize
      - reopened
  workflow_call:


jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Check out repository code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: 'pip'

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run test suite
        env:
          DJANGO_DEBUG: ${{ vars.DJANGO_DEBUG }}
          EMAIL_BACKEND: ${{ vars.EMAIL_BACKEND }}
          EMAIL_USER: ${{ vars.EMAIL_USER }}
          EMAIL_PASSWORD: ${{ vars.EMAIL_USER }}
          EMAIL_HOST: ${{ vars.EMAIL_HOST }}
          DJANGO_ALLOWED_HOSTS: ${{ vars.DJANGO_ALLOWED_HOSTS }}
          DJANGO_SECRET_KEY: $ {{ vars.EMAIL_HOST }}

        run: python -m pytest

Deployment auf Staging

Folgender Workflow wird ausgeführt, sobald der Develop-Branch einen neuen Commit erhält. Er sorgt dafür, dass der aktuelle Develop-Branch auf den Dokku-Server gepushed wird, wo dann das Dokku-Framework das Bauen und Deployen in die Staging-Umgebung übernimmt.

Da es zwei Deploy-Workflows gibt, macht es Sinn, den Workflow einmal zu definieren und die Unterschiede konfigurierbar zu machen:

name: 'Deploy to Dokku'

on:
  workflow_call:
    inputs:
      branch:
        required: true
        type: string
      dokku_app:
        required: true
        type: string
    secrets:
      DOKKU_SSH_PRIVATE_KEY:
        required: true

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Cloning repo
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Push to Dokku
        uses: dokku/github-action@master
        with:
          branch: ${{ inputs.branch }}
          git_remote_url: 'ssh://dokku@${{ secrets.DOKKU_HOST }}:22/${{ inputs.dokku_app }}'
          ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
name: 'Deploy staging'

# yamllint disable-line rule:truthy
on:
  push:
    branches:
      - develop

jobs:
  test:
    name: Test
    uses: ./.github/workflows/test.yml

  deploy:
      name: "Deploy new version"
      uses: ./.github/workflows/deploy.yml
      with:
        branch: "develop"
        dokku_app: "staging-app"
      secrets: inherit
      needs:
        - test

Im zweiten YAML-File müssen dadurch nur noch die entsprechenden Variablen gesetzt werden, um den richtigen Branch in die richtige App zu deployen. In diesem Fall ist es von «develop» auf «staging».

Deployment auf Production

Dieser Workflow wird ausgeführt, sobald der Release-Branch einen neuen Commit erhält. Er sorgt dafür, dass der aktuelle Release-Branch auf dem Dokku-Server gepushed wird, wo dann das Dokku-Framework das Bauen und Deployen in die Production-Umgebung übernimmt. Der einzige Unterschied zum Staging-Deployment ist der Branch und die Ziel-App:

name: 'Deploy release'

on:
  push:
    branches:
      - release

jobs:
  test:
    name: Test
    uses: ./.github/workflows/test.yml

  deploy:
      uses: ./.github/workflows/deploy.yml
      with:
        branch: "release"
        dokku_app: "production-app"
      secrets: inherit
      needs:
        - test

Persistenz

Eine spezielle Erwähnung verdient das Behandeln der Persistenz von Daten. Die allermeisten Web-Apps sind nicht statisch, sondern haben ein Persistenz-Layer, in dem Daten abgelegt und auch wieder abgerufen werden können. Oft, auch in diesem Fall, dient dazu eine Datenbank. Das Verknüpfen einer Web-Applikation mit der dazugehörigen Datenbank übernimmt Dokku, indem es intern einen Container mit einer Datenbank-Applikation laufen lässt und Applikation und Datenbank ins selbe Docker-Netzwerk steckt.

Das ist sehr bequem, bringt aber für die vorgesehene Architektur mit Staging- und Production-Umgebung Probleme mit sich. Folgende Szenarien sind denkbar:

Beide Szenarien haben gravierende Nachteile, die so nicht akzeptiert werden können.

Als Kompromisslösung habe ich folgende Vorgehensweise gewählt:

Damit kann eine Einflussnahme von Staging auf Production ausgeschlossen werden und Tests in der Staging-Umgebung sind ohne Gefahr möglich. Durch die Kopie der aktuellen Daten ist trotzdem sichergestellt, dass in der Staging-Umgebung alle Effekte sichtbar sind, die nach einem Update der Production-Umgebung auftreten werden.

Das Kopieren der Datenbank wird in die «Deployment auf Staging» GitHub-Action aufgenommen. Damit ist sichergestellt, dass bei jedem Update der Staging-Umgebung auch die aktuelle Datenbank der Produktionsumgebung kopiert wurde. Das wird erreicht, indem der Job prepare-environment zum Staging-Deployment hinzugefügt wird:

prepare-environment:
   runs-on: ubuntu-latest
   steps:
     - name: "Configure SSH"
       run: |
         mkdir -p ~/.ssh/
         echo "$SSH_KEY" > ~/.ssh/staging.key
         chmod 600 ~/.ssh/staging.key
         cat >>~/.ssh/config <<END
         Host staging
           HostName $SSH_HOST
           User $SSH_USER
           IdentityFile ~/.ssh/staging.key
           StrictHostKeyChecking no
         END
       env:
         SSH_USER: "dokku"
         SSH_KEY: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
         SSH_HOST: ${{ secrets.DOKKU_HOST }}
     - name: "Reset staging database"
       run: |
         ssh staging 'ps:stop staging-app'
         ssh staging 'postgres:unlink staging-db staging-app'
         ssh staging 'postgres:destroy staging-db -f'
         ssh staging 'postgres:clone production-db staging-db'
         ssh staging 'postgres:link staging-db staging-app'

Der Job ist etwas komplizierter, da mehrere SSH-Befehle durchgeführt werden müssen. Damit nicht bei jedem Befehl die Login-Daten separat geschickt werden, wird der SSH-Zugriff zuerst konfiguriert.

Damit ergibt sich für alle Actions folgender Ablauf:

GithHub Actions Workflow, dargestellt als FlowChart

GitHub Actions Workflow bei einem neuen Commit

 

Fazit

Das in diesem Beitrag beschriebene Setup setze ich erfolgreich für den Betrieb einer Vereinswebsite ein. Ich kann damit ohne grosses Budget eine CI/CD-Pipeline betreiben, die das Testen und Veröffentlichen der Website sehr vereinfacht.

Das Setup ist natürlich noch ausbaubar. Vor allem gewisse Systemtests wären noch sehr schön, damit die wichtigsten Website-Funktionen auch sicher reibungslos funktionieren. Trotzdem bin ich aktuell sehr zufrieden mit dem Setup. Vor allem die Tatsache, dass das Veröffentlichen jetzt quasi per Knopfdruck geschieht, spart viel Zeit und war den Aufwand wert.

Kommentare

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden

Copyright © 2025 Noser Engineering AG – Alle Rechte vorbehalten.

NACH OBEN
Privacy Policy Cookie Policy
Zur Webcast Übersicht