Schon mal darüber nachgedacht einen Docker Container selbst zu erstellen? Also ein Grundsystem mit allen Programmen oder Scripten die man gerne in so einer Kombination sehen möchte? Mit Forgejo lässt sich das komplett in der eigenen Infrastruktur abbilden: Git-Repository, automatischer Build-Prozess per CI/CD und eine eigene Container Registry – alles unter einem Dach.
Dieser Beitrag zeigt Schritt für Schritt wie es geht, am Beispiel eines Alpine-basierten Images mit vorinstallierten Bildbearbeitungs- und OCR-Tools.
Was wir bauen #
- Ein Dockerfile mit mehreren vorinstallierten Tools (Tesseract, Ghostscript, etc.)
- Ein Forgejo-Repository als Heimat für den Code
- Einen Forgejo Actions Workflow, der bei jedem Push automatisch baut
- Das fertige Image landet in der Forgejo Container Registry – pullbar von überall
Voraussetzungen #
- Eine laufende Forgejo-Instanz (z.B. hinter Caddy als Reverse Proxy)
- Ein laufender Forgejo Actions Runner mit Zugriff auf den Docker Socket
- Docker auf dem Server
Schritt 1: Der Forgejo Actions Runner #
Der Runner muss Zugriff auf den Docker Socket des Hosts haben, damit er Images bauen kann.
In der compose.yaml sieht das so aus:
services:
forgejo-runner:
image: code.forgejo.org/forgejo/runner:9
container_name: forgejo-runner
command: ["/bin/forgejo-runner", "daemon", "--config", "/data/config.yml"]
user: "0:0"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./runner-data:/data
restart: unless-stoppedWichtig ist außerdem die Runner-Konfiguration (runner-data/config.yml). Dort muss der
Docker-Socket für Job-Container freigegeben werden:
container:
docker_host: "automount"
valid_volumes:
- '**'Mit docker_host: "automount" wird der Socket automatisch in jeden Job-Container
durchgereicht – ohne das scheitern alle Docker-Befehle im Workflow.
Schritt 2: Das Dockerfile #
Ein gutes Dockerfile für ein Tool-Image nutzt Multi-Stage-Builds: in Stage 1 werden Abhängigkeiten kompiliert, Stage 2 enthält nur das Laufzeit-Minimum.
# Stage 1: build
FROM python:3.12-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential gcc \
zlib1g-dev libjpeg-dev libpng-dev libzbar-dev \
tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng \
ghostscript qpdf ca-certificates \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Stage 2: runtime
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng \
ghostscript qpdf libzbar0 libjpeg62-turbo libpng16-16 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# Nur die eigene App ins Image, keine nutzerspezifischen Scripts
COPY api.py /app/api.py
ENV PYTHONUNBUFFERED=1
EXPOSE 51822
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "51822"]Der Trick: nutzerspezifische Scripts kommen nicht ins Image, sondern werden später per Volume gemountet. Das Image bleibt dadurch generisch und wiederverwendbar.
Schritt 3: Repository-Struktur #
mein-tool/
├── .forgejo/
│ └── workflows/
│ └── build.yml # CI/CD Workflow
├── dockerbuild/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── api.py # Fester Bestandteil des Images
├── scripts/ # Beispiel-Scripts, per Volume gemountet
│ └── example_script.py
└── docker-compose.ymlSchritt 4: Secrets anlegen #
Damit der Workflow sich an der Container Registry anmelden kann, braucht er Zugangsdaten. Diese werden als Secrets im Repository hinterlegt:
In Forgejo → Repository → Settings → Secrets → Actions:
REGISTRY_USER→ Forgejo-BenutzernameREGISTRY_TOKEN→ Personal Access Token mitpackage:write-Berechtigung (Forgejo Profil → Settings → Applications)
Schritt 5: Der Workflow #
# .forgejo/workflows/build.yml
name: Build & Push Docker Image
on:
push:
branches:
- master
jobs:
build:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Docker CLI
run: |
apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
. /etc/os-release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian ${VERSION_CODENAME} stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update && apt-get install -y docker-ce-cli
- name: Login to Forgejo Container Registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login forgejo.example.com \
--username "${{ secrets.REGISTRY_USER }}" \
--password-stdin
- name: Build and Push
run: |
docker build \
-t forgejo.example.com/${{ github.repository_owner }}/mein-tool:latest \
-f dockerbuild/Dockerfile \
./dockerbuild
docker push forgejo.example.com/${{ github.repository_owner }}/mein-tool:latestAb jetzt passiert folgendes bei jedem git push auf master:
- Forgejo Actions startet den Workflow
- Der Runner installiert den Docker CLI
- Er meldet sich an der eigenen Container Registry an
- Das Image wird gebaut und gepusht
Schritt 6: Den Container nutzen #
Einmalig auf dem Zielsystem einloggen:
docker login forgejo.example.comDie docker-compose.yml auf dem Zielsystem:
services:
mein-tool:
image: forgejo.example.com/BENUTZERNAME/mein-tool:latest
container_name: mein-tool
volumes:
- ./import:/data/import:z
- ./output:/data/output:z
- ./scripts:/app/scripts:z # Scripts von außen mounten
ports:
- "51822:51822"Image aktualisieren:
docker compose pull && docker compose up -dWas jetzt noch möglich ist #
Das ist erst der Anfang. Mit demselben Runner lassen sich noch viele weitere Dinge automatisieren:
Verschiedene Image-Tags: Mit Git-Tags lassen sich stabile Versionen einfrieren.
Ein v1.0-Tag baut mein-tool:v1.0 zusätzlich zu latest.
Automatische Tests: Vor dem Push kann der Workflow Tests ausführen – schlägt ein Test fehl, wird kein kaputtes Image gepusht.
Multi-Arch-Images: Mit docker buildx lassen sich Images für amd64 und arm64
gleichzeitig bauen – praktisch wenn der Server x86 ist, aber z.B. Raspberry Pis das
Image pullen sollen.
Andere Sprachen und Tools: Der Runner ist nicht auf Docker beschränkt. Hugo-Blogs bauen und deployen, Go-Binaries kompilieren, Python-Tests mit pytest, Markdown-Linting – alles was in einem Debian-Container läuft, läuft auch im Workflow.
Benachrichtigungen: Bei fehlgeschlagenen Builds lässt sich eine Benachrichtigung per Webhook, Matrix oder Gotify verschicken.
Fazit #
Mit Forgejo, einem Actions Runner und ein paar Zeilen YAML ist ein vollständiger selbstgehosteter Build- und Distributionsprozess für Docker Images aufgebaut. Kein Docker Hub, keine externe Registry, keine Abhängigkeit von Drittdiensten.
Der Aufwand für die Einrichtung zahlt sich schnell aus: jede Änderung am Code landet
nach einem git push automatisch als fertiges Image in der eigenen Registry –
pullbar von jedem System das Zugriff auf die Forgejo-Instanz hat.