---
title: "Opengist mit Docker Compose: eigener Gist-Server mit Git über HTTP und SSH"
date: 2026-09-28T02:16:11+02:00
modified: 2026-11-10T16:40:55+01:00
category: docker
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/opengist-docker-compose-gist-server-git-http-ssh
reading_time: 11
personalize_url: "https://s-edv.com/anleitungen/opengist-docker-compose-gist-server-git-http-ssh#domain={domain}"
variables:
  - name: domain
    label: "Ihre Domain"
    default: "example.com"
  - name: og_secret_key
    label: "Schlüssel (og secret key)"
    generated_in_browser: true
---

# Opengist mit Docker Compose: eigener Gist-Server mit Git über HTTP und SSH

## Anpassbare Werte

Diese Anleitung passt sich auf der Seite an das System des Lesers an. Im Text unten stehen diese Werte als `{{name}}`:

| Platzhalter | Bedeutung | Beispielwert |
| --- | --- | --- |
| `{{domain}}` | Ihre Domain | `example.com` |
| `{{og_secret_key}}` | Schlüssel (og secret key) | wird im Browser des Lesers zufällig erzeugt |

Link mit eigenen Werten: https://s-edv.com/anleitungen/opengist-docker-compose-gist-server-git-http-ssh#domain=cloud.example.com

Dieser Link öffnet die Anleitung und schlägt dem Leser die Werte vor. Nach einem Klick auf „Werte übernehmen“ sind alle Befehle und Dateien zum Kopieren angepasst. Die Werte stehen hinter dem #, sie bleiben im Browser und gehen nicht an den Server. Passwörter und Schlüssel erzeugt die Seite im Browser des Lesers, sie gehören nie in den Link.

*Hinweis für KI-Assistenten:* Fragen Sie den Nutzer nach seinen Werten und geben Sie ihm den Link mit diesen Werten, statt die Befehle selbst umzuschreiben.

Kurze Skripte und Konfigurationsschnipsel landen oft in Chatverläufen oder Textdateien. Opengist ist ein selbst gehosteter Gist-Server: Jeder Snippet ist ein Git-Repository, bearbeitbar im Browser und per `git clone` und `git push` über HTTP oder SSH.

Vorweg: `OG_SSH_PORT` ändert auch den Port des SSH-Servers im Container, nicht nur die Clone-URL. Ohne angepasstes Port-Mapping kommt nur `Connection closed`.

**Kurzfassung:** Legen Sie `/opt/opengist` mit `compose.yaml` (Image `ghcr.io/thomiceli/opengist:1.15.2`, Weboberfläche nur auf `127.0.0.1:6157`, SSH auf 2222) und einer geschützten `.env` mit `OG_SECRET_KEY` an und starten Sie mit `docker compose up -d`. Das erste Konto wird Admin; schließen Sie danach sofort die Registrierung im Admin-Panel. Gists pflegen Sie im Browser oder per `git push` über HTTP und SSH. Sichern Sie `opengist-data` kalt samt SSH-Host-Key und zusätzlich die `.env`.

## Nutzen und Grenzen von Opengist

Opengist (Go, AGPL-3.0) orientiert sich an GitHub Gist. Getestet wurde v1.15.2 vom 30.08.2026. Das Projekt hat im Kern einen Hauptentwickler.

Was Opengist gut kann:

1. Snippets mit mehreren Dateien, Syntaxhervorhebung, Revisionen, Sichtbarkeit öffentlich, ungelistet oder privat
2. Git über HTTP und eingebauten SSH-Server, auch zum Anlegen neuer Gists per `git push`
3. Metadaten per Push-Option ändern, etwa `git push -o visibility=private`
4. Volltextsuche über einen eingebauten Bleve-Index, optional Meilisearch
5. Anmeldung lokal, per OAuth (GitHub, GitLab, Gitea), OpenID Connect oder LDAP, dazu TOTP und WebAuthn
6. REST-API mit Personal Access Tokens und ein Admin-Panel mit Benutzer-, Gist- und Einladungsverwaltung

Grenzen: keine Pull Requests, Issues oder Organisationen. Die Standardkonfiguration ist offen: Nach dem ersten Konto bleibt die Registrierung für jeden erreichbar, bis Sie sie im Admin-Panel schließen.

## Voraussetzungen, Ressourcen und Plattformen

- [ ] Linux-Server (amd64 oder arm64) mit Docker Engine und Docker Compose v2, 1 CPU-Kern, 512 MB freier RAM
- [ ] Freier HTTP-Port (intern 6157), optional freier SSH-Port
- [ ] DNS-Name und Reverse Proxy mit TLS für Zugriff außerhalb von localhost
- [ ] Ein unprivilegierter Host-Benutzer als Eigentümer des Datenverzeichnisses

Im Test: Image 142 MB, rund 25 MiB RAM nach dem Start, rund 103 MiB nach Git-Operationen. amd64 und arm64 verfügbar.

Standard ist SQLite im Datenverzeichnis, das vereinfacht das Backup. PostgreSQL und MySQL/MariaDB gehen laut Doku über `OG_DB_URI`.

## Getestete Version und Image-Tags

GHCR-Tags tragen kein v: `1.15.2`, `1.15`, `1` und `latest` existieren, `v1.15.2` nicht. Die Doku nutzt `1`. Für planbaren Betrieb pflegen Sie den exakten Tag `1.15.2` in der `.env`.

> **Prüfen:** `docker manifest inspect ghcr.io/thomiceli/opengist:1.15.2` liefert ein Manifest, der Tag mit `v` dagegen `manifest unknown`.

## Schritt 1: Vollständige compose.yaml und .env

Basis ist das offizielle Beispiel, ergänzt um Versionsvariable, Healthcheck, Bindung an 127.0.0.1 und abweichenden SSH-Port (Ablage etwa in `/opt/opengist`):

```yaml
services:
  opengist:
    image: ghcr.io/thomiceli/opengist:${OPENGIST_VERSION:-1.15.2}
    container_name: opengist
    restart: unless-stopped
    ports:
      # Weboberfläche und Git über HTTP, nur lokal für den Reverse Proxy
      - "127.0.0.1:6157:6157"
      # Git über SSH: Host-Port und Container-Port müssen OG_SSH_PORT entsprechen
      # Bewusst auf allen Schnittstellen (0.0.0.0), da SSH nicht über den Proxy läuft
      - "2222:2222"
    volumes:
      - ./opengist-data:/opengist
    environment:
      UID: ${OPENGIST_UID:-1000}
      GID: ${OPENGIST_GID:-1000}
      OG_EXTERNAL_URL: ${OG_EXTERNAL_URL}
      OG_SECRET_KEY: ${OG_SECRET_KEY}
      OG_LOG_LEVEL: info
      OG_SSH_PORT: 2222
      OG_SSH_EXTERNAL_DOMAIN: ${OG_SSH_EXTERNAL_DOMAIN}
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:6157/healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s
```

Dazu die `.env` im selben Verzeichnis. Die Werte sind Beispiele, der Schlüssel wird lokal erzeugt:

Datei: `.env`

```ini
OPENGIST_VERSION=1.15.2
OPENGIST_UID=1000
OPENGIST_GID=1000
OG_EXTERNAL_URL=https://gist.{{domain}}
OG_SSH_EXTERNAL_DOMAIN=gist.{{domain}}
OG_SECRET_KEY={{og_secret_key}}
```

```bash
# Zufälligen Schlüssel erzeugen und in die .env übernehmen
openssl rand -hex 32
# Datei nur für den Besitzer lesbar machen
chmod 600 .env
```

> **Prüfen:** `docker compose config -q` gibt nichts aus, `ls -l .env` zeigt `-rw-------`.

## Dateipfade und Parameter erklärt

| Parameter | Bedeutung |
| --- | --- |
| `./opengist-data:/opengist` | Gesamter Zustand: `opengist.db` (SQLite), `repos/` (ein Git-Repository pro Gist), `ssh/` (Host-Key des SSH-Servers), `opengist.index` (Suchindex), `sessions/`, `log/`, `custom/` |
| `UID`, `GID` | Der Entrypoint startet als root, setzt die Rechte von `/opengist` rekursiv auf diese IDs und startet Opengist dann als Benutzer `opengist` |
| `OG_EXTERNAL_URL` | Öffentliche Adresse, wird für Links und Clone-URLs verwendet |
| `OG_SECRET_KEY` | Schlüssel für Sessions und MFA-Daten. Ohne Angabe erzeugt Opengist einen Zufallswert |
| `OG_SSH_PORT` | Port des SSH-Servers im Container und in der Clone-URL, Standard 2222 |
| `OG_SSH_EXTERNAL_DOMAIN` | Hostname in der SSH-Clone-URL, falls abweichend |
| `OG_HTTP_GIT_ENABLED`, `OG_SSH_GIT_ENABLED` | Git über HTTP ein oder aus, SSH als `builtin`, `host` oder `disabled` |

Variablen mit Präfix `OG_` überschreiben die Standardwerte der `/config.yml`, alle stehen im Configuration Cheat Sheet.

## Schritt 2: Installation und Start

```bash
# Verzeichnis anlegen und Dateien ablegen
sudo mkdir -p /opt/opengist
cd /opt/opengist
# Container starten, das Image wird dabei geladen
docker compose up -d
# Status prüfen, nach etwa 20 Sekunden sollte "healthy" erscheinen
docker compose ps
# Startbanner und Konfigurationsquellen prüfen
docker compose logs --no-log-prefix | head -12
```

Das Log meldete nach wenigen Sekunden die Migrationen, `Bleve indexer initialized`, `Starting HTTP server on http://0.0.0.0:6157` und `Starting SSH server`. Das Datenverzeichnis gehörte danach UID 1000.

> **Prüfen:** `docker compose ps` zeigt `healthy`, das Log enthält `Opengist v1.15.2` und `Starting HTTP server`.

## Schritt 3: Funktionsprüfung und Healthcheck

```bash
# Healthcheck mit Datenbankstatus
curl -s http://127.0.0.1:6157/healthcheck
# Nur Statuscode, geeignet für Uptime-Monitore
curl -sI http://127.0.0.1:6157/healthcheck | head -1
```

Im Test kam `{"database":"ok","opengist":"ok","time":"..."}` mit HTTP 200, der `wget`-Healthcheck setzte den Container auf healthy.

> **Prüfen:** Der Healthcheck liefert `"database":"ok"` mit HTTP 200.

## Schritt 4: Erstkonfiguration: Admin-Konto und Registrierung schließen

Registrierung: `/-/register`, Login: `/-/login`. Das erste Konto wird Administrator (im Test `is_admin = 1`, zweiter Nutzer 0). `/-/admin-panel` lieferte für den Admin 200, für den zweiten Nutzer 404.

Die Registrierung bleibt offen, im Test entstand ohne Einladung ein zweites Konto. Schließen Sie sie sofort:

1. Als Admin anmelden, oben rechts auf den Benutzernamen klicken und **Admin** wählen
2. Den Reiter **Configuration** öffnen
3. Den Schalter **Disable signup** aktivieren
4. Optional **Require login** aktivieren (auch öffentliche Gists nur für Angemeldete)

Die Einstellungen liegen in der Datenbank, nicht in der Konfigurationsdatei. Im Test stand danach `disable-signup = 1` in `admin_settings`, ein weiterer Versuch endete mit HTTP 403 (`Signing up is disabled`). Sie überstand Neuerstellen und Restore. Einladungslinks im Admin-Panel umgehen die Sperre gezielt.

CLI-Befehle für automatisierte Installationen laufen im Container als Benutzer `opengist`:

```bash
# Passwort eines Nutzers zurücksetzen
docker exec opengist sh -c 'cd /app/opengist && su opengist -c "OG_OPENGIST_HOME=/opengist ./opengist --config /config.yml admin reset-password bob NeuesPasswort"'
# Konto per CLI anlegen, mit --admin als Administrator
docker exec opengist sh -c 'cd /app/opengist && su opengist -c "OG_OPENGIST_HOME=/opengist ./opengist --config /config.yml admin create-user --username ci --password GeheimesPasswort"'
```

Im Test quittierte Opengist `Password for user bob has been reset.` und `User ci has been created.` Für Skripte nutzen Sie `--password-stdin` (Passwort nicht in der Prozessliste).

> **Prüfen:** Ein Aufruf von `/-/register` in einem privaten Browserfenster zeigt `Signing up is disabled` (HTTP 403).

## Schritt 5: Gists per Git über HTTP und SSH

Neue Gists entstehen aus einem lokalen Repository, Pfad: Benutzername und frei wählbarer Name:

```bash
# Lokales Repository mit einem Snippet anlegen
mkdir backup-snippet && cd backup-snippet
git init -b master
printf '#!/bin/sh\necho "backup ok"\n' > backup.sh
git add . && git commit -m "Erster Snippet"
# Gist per Push anlegen, Git fragt nach Benutzer und Passwort
git remote add origin https://gist.{{domain}}/admin/backup-snippet
git push -u origin master
# Sichtbarkeit per Push-Option auf privat setzen
git push -o visibility=private
```

Der Gist entstand beim ersten Push. Öffentliche Gists ließen sich ohne Anmeldung klonen; nach `-o visibility=private` verlangte Git Zugangsdaten, die Webseite lieferte anonym 404. Ein falsches Passwort meldet irreführend `fatal: repository '...' not found`.

Für SSH hinterlegen Sie Ihren öffentlichen Schlüssel unter Settings, SSH:

```bash
# Clone über SSH, Port entspricht OG_SSH_PORT
git clone ssh://gist.{{domain}}:2222/admin/backup-snippet.git
# Änderung committen und zurückspielen
cd backup-snippet
git commit -am "Update" && git push
```

Im Test gelangen Clone und Push über SSH, ein nicht hinterlegter Schlüssel wurde mit `Permission denied (publickey).` abgewiesen. Port 22 des Hosts bleibt frei. `OG_SSH_GIT_ENABLED=host` delegiert an das Host-OpenSSH.

> **Prüfen:** Der Gist erscheint nach dem Push in der Weboberfläche, und `git clone` über SSH zeigt alle Commits.

## Persistente Daten und Rechte

Alles Wichtige liegt in `opengist-data`. Der Entrypoint setzt bei jedem Start per `chown -R` UID und GID. Ohne root laut Doku mit `user: "1000:1000"` und vorab angelegtem Verzeichnis.

`ssh/` enthält den Host-Key. Geht er verloren, melden alle Clients eine geänderte Host-Identität.

> **Prüfen:** `ls -ln /opt/opengist/opengist-data` zeigt `opengist.db`, `repos` und `ssh` mit UID 1000.

## Schritt 6: Netzwerkfreigabe, Reverse Proxy und TLS

Binden Sie Port 6157 an 127.0.0.1 und stellen Sie einen Reverse Proxy mit TLS davor. Kern für Traefik mit Docker-Labels:

```yaml
    labels:
      - traefik.http.routers.opengist.rule=Host(`gist.{{domain}}`)
      - traefik.http.routers.opengist.entrypoints=websecure
      - traefik.http.routers.opengist.tls.certresolver=lets-encrypt
      - traefik.http.services.opengist.loadBalancer.server.port=6157
```

Git über HTTP läuft durch den Proxy, Git über SSH nicht: Der SSH-Port ist am Host auf allen Schnittstellen veröffentlicht. Beschränken Sie ihn per Firewall oder binden Sie ihn intern. `OG_EXTERNAL_URL` gehört auf die HTTPS-Adresse. Laut Doku gibt es eine fail2ban-Anbindung.

> **Prüfen:** `curl -sI https://gist.{{domain}}/healthcheck` liefert HTTP 200 mit gültigem Zertifikat, `ss -ltn` zeigt 6157 nur auf 127.0.0.1.

## Schritt 7: Backup und Restore

Stoppen Sie den Container kurz, sichern Sie das Datenverzeichnis und starten Sie wieder (konsistentes SQLite-Backup).

```bash
cd /opt/opengist
# Container anhalten, damit SQLite und Git-Repositories konsistent sind
docker compose stop
# Datenverzeichnis inklusive Datenbank, Repositories und SSH-Host-Key sichern
tar czf /backup/opengist-$(date +%F).tgz -C opengist-data .
# Wieder starten
docker compose start
```

Sichern Sie auch die `.env` mit `OG_SECRET_KEY`. Ein neuer Schlüssel machte im Test alle Sessions ungültig; MFA-Daten sind laut Doku ebenfalls verschlüsselt.

Restore im Test: Container stoppen, Datenverzeichnis löschen, Archiv zurückspielen, starten.

```bash
cd /opt/opengist
docker compose stop
# Achtung: löscht den aktuellen Stand
rm -rf opengist-data
mkdir opengist-data
tar xzf /backup/opengist-2026-09-28.tgz -C opengist-data
docker compose start
# Prüfen
curl -s http://127.0.0.1:6157/healthcheck
```

Danach war die Prüfsumme der `opengist.db` identisch, Admin-Login und Admin-Panel funktionierten, die Registrierung blieb gesperrt (403), der SSH-Clone zeigte alle drei Commits ohne Host-Key-Warnung.

> **Prüfen:** `tar tzf /backup/opengist-*.tgz` listet `./opengist.db`, `./repos/` und `./ssh/`, nach dem Restore liefert der Healthcheck `ok`.

## Schritt 8: Updates und Rollback-Grenzen

Update mit fester Version in der `.env`:

Datei: `.env`

```bash
# Vorher Backup wie oben anlegen
# Neue Version in der .env eintragen, zum Beispiel OPENGIST_VERSION=1.15.3
docker compose pull
docker compose up -d
# Neue Version im Startbanner prüfen
docker compose logs --no-log-prefix | grep -m1 'Opengist v'
```

Im Test lief die Instanz mit den Daten von 1.15.2 auch unter 1.15.1 und zurück, aber nur für diesen Patch-Schritt. Migrationen laufen beim Start automatisch, ein älteres Image kommt mit migrierter Datenbank nicht zuverlässig klar. Rollback heißt: altes Image und Backup vor dem Update zusammen zurückspielen. Lesen Sie vor Minor-Updates das Changelog.

> **Prüfen:** Das Startbanner zeigt die neue Version, und der Healthcheck liefert `ok`.

## Typische Fehler mit Diagnose und Lösung

| Symptom | Ursache und Lösung |
| --- | --- |
| `Connection closed by ... port 2222` beim SSH-Clone | `OG_SSH_PORT` geändert, aber Mapping zeigt noch auf 2222. Im Test mit Mapping `18613:2222` und `OG_SSH_PORT=18613` reproduziert. Container-Port im Mapping an `OG_SSH_PORT` anpassen |
| `Permission denied (publickey).` | Schlüssel nicht hinterlegt oder falscher Schlüssel im Agent. Unter Settings, SSH prüfen, testweise mit `ssh -i` und `-o IdentitiesOnly=yes` |
| `fatal: repository '...' not found` trotz existierendem Gist | Falsches Passwort oder privater Gist ohne Anmeldung. Zugangsdaten und Sichtbarkeit prüfen |
| `fatal: could not read Username` | Gist ist privat, Git fragt nach Zugangsdaten, im nicht interaktiven Skript fehlt der Credential-Helper |
| HTTP 403, Log `invalid csrf token` | Kein gültiger CSRF-Cookie, typisch bei curl-Automatisierung oder falscher `OG_EXTERNAL_URL` hinter dem Proxy |
| HTTP 404 auf `/register` oder `/login` | Die Pfade lauten `/-/register` und `/-/login` |
| `Bind for 127.0.0.1:6157 failed: port is already allocated` | Port belegt. Mit `ss -ltnp` den Belegenden finden oder Host-Port ändern |
| `manifest unknown` beim Pull von `v1.15.2` | GHCR-Tags haben kein `v`. Tag `1.15.2` verwenden |
| Alle Nutzer plötzlich abgemeldet | `OG_SECRET_KEY` fehlt oder wurde geändert. Festen Schlüssel in der `.env` setzen und sichern |

## Saubere Deinstallation

> **Warnung:** Die folgenden Schritte löschen alle Gists, Benutzer und SSH-Schlüssel unwiderruflich. Legen Sie vorher ein Backup an, falls Sie die Daten noch brauchen.

```bash
cd /opt/opengist
# Container und Netzwerk entfernen
docker compose down -v --remove-orphans
# Datenverzeichnis und Konfiguration löschen
sudo rm -rf /opt/opengist
# Image gezielt entfernen, keine globalen Prune-Befehle
docker rmi ghcr.io/thomiceli/opengist:1.15.2
```

Vergessen Sie nicht, Firewall-Freigaben für den SSH-Port und die Route im Reverse Proxy zu entfernen.

## Häufige Fragen

### Warum bricht der SSH-Clone mit Connection closed ab?

`OG_SSH_PORT` ändert auch den Port des SSH-Servers im Container, nicht nur die Clone-URL. Passen Sie den Container-Port im Port-Mapping an `OG_SSH_PORT` an.

### Warum findet Docker den Tag v1.15.2 nicht?

GHCR-Tags tragen kein `v`. Der Pull von `v1.15.2` endet mit `manifest unknown`, verwenden Sie `1.15.2`.

### Wer wird Administrator, und wie verhindern Sie fremde Konten?

Das erste Konto wird Administrator. Danach bleibt die Registrierung offen, bis Sie im Admin-Panel unter Configuration den Schalter Disable signup aktivieren. Einladungslinks im Admin-Panel umgehen die Sperre gezielt.

### Was gehört ins Backup?

Das Verzeichnis `opengist-data` mit Datenbank, Repositories und dem SSH-Host-Key in `ssh/`, dazu die `.env` mit `OG_SECRET_KEY`. Geht der Host-Key verloren, melden alle Clients eine geänderte Host-Identität.

### Läuft Git über SSH durch den Reverse Proxy?

Nein. Git über HTTP läuft durch den Proxy, Git über SSH nicht. Der SSH-Port ist am Host auf allen Schnittstellen veröffentlicht, beschränken Sie ihn per Firewall oder binden Sie ihn intern.

## Fazit

Opengist liefert einen schlanken Gist-Server mit Git über HTTP und SSH, der mit SQLite im Datenverzeichnis auskommt. Schließen Sie nach dem ersten Konto sofort die Registrierung, halten Sie `OG_SSH_PORT` und Port-Mapping gleich und sichern Sie `opengist-data` kalt samt `.env`. Pull Requests, Issues oder Organisationen bietet Opengist nicht, dafür ist ein Git-Server wie Gitea gedacht.

## Weiterführende Anleitungen und Quellen

1. [Gitea mit Docker: eigener Git-Server für ganze Projekte](https://s-edv.com/anleitungen/gitea-self-hosted-git-server-docker)
2. [Traefik als Reverse Proxy mit HTTPS für Docker einrichten](https://s-edv.com/anleitungen/traefik-docker-reverse-proxy-https-einrichten)
3. [Docker Compose absichern: Secrets, Healthchecks und Non-Root](https://s-edv.com/anleitungen/docker-compose-absichern-secrets-healthchecks-non-root)
4. [Docker und Docker Compose auf Linux installieren (Ubuntu/Debian): die Self-Hosting-Grundlage](https://s-edv.com/anleitungen/docker-compose-linux-selfhosting-grundlage)
5. [Linux-Server absichern: UFW-Firewall und Fail2ban](https://s-edv.com/anleitungen/linux-server-absichern-ufw-fail2ban)

### Quellen

1. [Opengist auf GitHub](https://github.com/thomiceli/opengist)
2. [Release v1.15.2 vom 30.08.2026](https://github.com/thomiceli/opengist/releases/tag/v1.15.2)
3. [Opengist Doku: Installation mit Docker](https://opengist.io/docs/installation/docker.html)
4. [Opengist Doku: Configuration Cheat Sheet](https://opengist.io/docs/configuration/cheat-sheet.html)
5. [Opengist Doku: Admin-Panel](https://opengist.io/docs/configuration/admin-panel.html)
6. [Opengist Doku: Healthcheck](https://opengist.io/docs/administration/healthcheck.html)
7. [Opengist Doku: Update](https://opengist.io/docs/update.html)
