Praxisleitfaden mit Docker oder Podman Compose, PostgreSQL, Memcached, Hocuspocus, Nginx Reverse Proxy und TLS

1. Einleitung

OpenProject ist eine leistungsfähige Open-Source-Plattform für Projektmanagement, Aufgabenverwaltung, Roadmaps, agile Boards, Gantt-Diagramme, Team-Kollaboration und Dokumentation. Die Software eignet sich für Organisationen, die Projekte transparent planen und steuern möchten, ohne vollständig von proprietären Plattformen abhängig zu sein.

Gerade im Kontext digitaler Souveränität spielt OpenProject eine wichtige Rolle. Viele Organisationen suchen nach Alternativen zu klassischen Atlassian-Umgebungen. In Kombination mit XWiki kann OpenProject eine offene Plattformlandschaft bilden: OpenProject übernimmt Projektmanagement, Tickets, Roadmaps und Aufgabensteuerung, während XWiki Wissensmanagement und technische Dokumentation abdeckt.

Dieser Beitrag beschreibt eine manuelle Installation von OpenProject als Container-Deployment. Der Fokus liegt bewusst nicht auf einer Paketinstallation über DEB oder RPM. OpenProject empfiehlt inzwischen die Docker-basierte Installation, und für skalierbare Umgebungen steht mit Helm auch ein Kubernetes-Weg zur Verfügung. Paketbasierte Installationen bleiben zwar für bestimmte Distributionen verfügbar, sind aber nicht mehr der zukunftssichere Standard für neue Linux-Versionen.

Als Beispiel verwenden wir:

  • Domain: projects.example.org
  • Runtime-User: openproject
  • Public URL: https://projects.example.org
  • OpenProject-Image: openproject/openproject:17-slim
  • PostgreSQL: Version 17
  • interner OpenProject-Port: 127.0.0.1:6000
  • interner Hocuspocus-Port: 127.0.0.1:6001
  • erster Administrator: Anna Berger
  • Projektleiter: Max Schneider

Die spätere Automatisierung dieser Schritte mit Ansible wird in einem folgenden Beitrag behandelt.

2. Ziel des Beitrags

Ziel ist eine produktionsnahe OpenProject-Installation, die sauber in mehrere Komponenten getrennt ist. OpenProject läuft nicht als einzelner All-in-one-Testcontainer, sondern als Compose-Stack mit getrennten Diensten.

Die Umgebung besteht aus:

  • OpenProject Web
  • OpenProject Worker
  • OpenProject Cron
  • OpenProject Seeder
  • PostgreSQL
  • Memcached
  • optional Hocuspocus für kollaborative Bearbeitung
  • Nginx als Reverse Proxy
  • TLS-Zertifikat für den öffentlichen Zugriff

Diese Trennung macht die Installation transparenter und langfristig besser wartbar. Web-Requests, Hintergrundjobs, Cron-Aufgaben, Datenbank, Cache und kollaborative Echtzeit-Komponenten laufen als getrennte Dienste und können getrennt beobachtet, aktualisiert und debuggt werden.

3. Warum Container statt klassischer Paketinstallation?

OpenProject unterstützt weiterhin paketbasierte Installationen für ausgewählte Distributionen. Gleichzeitig wird in der offiziellen Dokumentation klar, dass Container-basierte Installationen empfohlen werden. Außerdem weist OpenProject darauf hin, dass einige zukünftige Funktionen nur noch mit Container-basierten Installationen ausgeliefert werden sollen und dass für neuere Linux-Versionen keine neuen Paket-Repositories geplant sind.

Für neue produktive Setups ist ein Container-Deployment deshalb der sinnvollere Weg. Es reduziert Abhängigkeiten zum Host-Betriebssystem, vereinfacht Updates und passt besser zu modernen Betriebsmodellen mit Docker Compose, Podman Compose oder Kubernetes.

Die klassische Paketinstallation kann weiterhin sinnvoll sein, wenn:

  • eine offiziell unterstützte Distribution eingesetzt wird
  • ein dedizierter Server nur für OpenProject bereitsteht
  • die Paket-Wizard-Konfiguration bewusst genutzt werden soll
  • bestehende Betriebsprozesse DEB/RPM-Pakete bevorzugen

Für neue Umgebungen empfiehlt sich jedoch ein Container-Setup. In diesem Artikel wird daher ein manuelles Compose-Deployment gezeigt.

4. Zielarchitektur

Die Zielarchitektur trennt öffentlichen Zugriff, Applikationsdienste und persistente Daten.

bash
Client / Browser
|
| HTTPS
v
Nginx Reverse Proxy
|
| HTTP lokal :6000
v
OpenProject Web
|
+--> OpenProject Worker
+--> OpenProject Cron
+--> Memcached
+--> PostgreSQL
|
| WebSocket /hocuspocus
v
Hocuspocus

Nginx ist die einzige öffentlich erreichbare Komponente. Der OpenProject-Webcontainer lauscht nur lokal auf 127.0.0.1:6000. Hocuspocus, falls aktiviert, lauscht nur lokal auf 127.0.0.1:6001. PostgreSQL und Memcached bleiben vollständig im internen Compose-Netz.

Wichtige Prinzipien:

  • kein direkter öffentlicher Zugriff auf Container-Ports
  • TLS-Terminierung über Nginx
  • persistente PostgreSQL-Daten außerhalb der Container
  • persistente OpenProject-Assets außerhalb der Container
  • Secrets in einer geschützten .env-Datei
  • feste Image-Tags statt unkontrollierter Floating-Tags

5. Voraussetzungen

Für das Deployment wird ein Linux-Server mit Root- oder sudo-Rechten benötigt. Der DNS-Eintrag für projects.example.org sollte bereits auf den Server zeigen. Port 80 und 443 müssen erreichbar sein, damit HTTP-Weiterleitung und TLS funktionieren.

Benötigt werden:

  • Docker Compose oder Podman Compose
  • Nginx
  • Certbot
  • OpenSSL
  • ausreichend Speicherplatz für Datenbank, Assets und Backups
  • funktionierende DNS-Auflösung
  • ein SMTP-Relay für produktive E-Mails

In diesem Beitrag wird Podman Compose gezeigt. Wer Docker nutzt, kann die Compose-Datei weitgehend identisch verwenden und die Befehle entsprechend auf docker compose anpassen.

6. Host-Pakete installieren

Zuerst werden die Host-Pakete installiert. Je nach Distribution unterscheiden sich die Paketnamen leicht.

Debian-basierte Systeme

bash
apt update
apt upgrade -y
apt install -y podman podman-compose nginx certbot python3-certbot-nginx openssl uidmap curl

Red Hat-basierte Systeme

bash
dnf update -y
dnf install -y podman podman-compose nginx certbot python3-certbot-nginx openssl policycoreutils-python-utils curl

SUSE-basierte Systeme

bash
zypper refresh
zypper update -y
zypper install -y podman podman-compose nginx certbot python3-certbot-nginx openssl curl

Danach sollte geprüft werden, ob Podman und Compose verfügbar sind.

bash
podman --version
podman-compose --version
nginx -v

7. Benutzer und Verzeichnisstruktur vorbereiten

OpenProject erhält einen eigenen technischen Benutzer. Die Anwendung selbst läuft in Containern, aber Compose-Dateien, Assets, Backups und Datenbankverzeichnisse sollen auf dem Host sauber strukturiert abgelegt werden.

bash
groupadd --system openproject
useradd \
--system \
--home-dir /srv/openproject \
--shell /sbin/nologin \
--gid openproject \
openproject

Die Verzeichnisse werden getrennt nach Compose-Projekt, Assets, PostgreSQL-Daten und Backups angelegt.

containerfile
install -d -o openproject -g openproject -m 0755 /srv/openproject
install -d -o openproject -g openproject -m 0755 /srv/openproject/httpdocs
install -d -o openproject -g openproject -m 0750 /srv/openproject/logs
install -d -o openproject -g openproject -m 0750 /srv/openproject/scripts
install -d -o openproject -g openproject -m 0750 /srv/openproject/tmp
install -d -o openproject -g openproject -m 0755 /var/db/openproject
install -d -o openproject -g openproject -m 0755 /var/db/openproject/assets
install -d -o openproject -g openproject -m 0750 /var/db/openproject/backups
install -d -o openproject -g openproject -m 0755 /var/lib/postgresql/openproject

Auf SELinux-Systemen muss zusätzlich sichergestellt werden, dass die Container auf die persistenten Verzeichnisse zugreifen dürfen.

bash
semanage fcontext -a -t container_file_t '/var/db/openproject(/.*)?'
semanage fcontext -a -t container_file_t '/var/lib/postgresql/openproject(/.*)?'
restorecon -RFv /var/db/openproject /var/lib/postgresql/openproject


8. Secrets erzeugen

Für OpenProject werden mehrere Secrets benötigt: SECRET_KEY_BASE, das PostgreSQL-Passwort und bei aktivierter kollaborativer Bearbeitung ein Hocuspocus-Secret.

containerfile
install -d -o root -g openproject -m 0750 /srv/openproject/httpdocs
SECRET_KEY_BASE="$(openssl rand -hex 64)"
POSTGRES_PASSWORD="$(openssl rand -hex 24)"
COLLABORATIVE_SERVER_SECRET="$(openssl rand -hex 32)"
ADMIN_BOOTSTRAP_PASSWORD="$(openssl rand -hex 18)"

Diese Werte werden später in einer .env-Datei abgelegt. Die Datei muss geschützt werden, da sie Anwendungsschlüssel und Datenbankpasswörter enthält.

9. Environment-Datei erstellen

Die .env-Datei enthält Image-Tags, Datenbankparameter, Hostnamen, Cache-Konfiguration, Hocuspocus-Einstellungen und den initialen Admin-Benutzer.

bash
cat > /srv/openproject/httpdocs/.env <<EOF
TAG=17-slim
HOCUSPOCUS_TAG=17.4.0
POSTGRES_VERSION=17
POSTGRES_DB=openproject
POSTGRES_USER=openproject
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
SECRET_KEY_BASE=${SECRET_KEY_BASE}
DATABASE_URL=postgres://openproject:${POSTGRES_PASSWORD}@db/openproject?pool=20&encoding=unicode&reconnect=true
OPENPROJECT_HOST__NAME=projects.example.org
OPENPROJECT_HTTPS=true
OPENPROJECT_HSTS=true
OPENPROJECT_DEFAULT__LANGUAGE=de
OPENPROJECT_RAILS__CACHE__STORE=memcache
OPENPROJECT_CACHE__MEMCACHE__SERVER=cache:11211
OPENPROJECT_SEED__ADMIN__USER__NAME=Anna Berger
OPENPROJECT_SEED__ADMIN__USER__MAIL=anna.berger@example.org
OPENPROJECT_SEED__ADMIN__USER__PASSWORD=${ADMIN_BOOTSTRAP_PASSWORD}
OPENPROJECT_SEED__ADMIN__USER__PASSWORD__RESET=true
OPENPROJECT_SEED__ADMIN__USER__LOCKED=false
COLLABORATIVE_SERVER_SECRET=${COLLABORATIVE_SERVER_SECRET}
COLLABORATIVE_SERVER_URL=wss://projects.example.org/hocuspocus
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL=wss://projects.example.org/hocuspocus
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET=${COLLABORATIVE_SERVER_SECRET}
OPENPROJECT_INTERNAL_URL=http://web:8080
EOF
chown root:openproject /srv/openproject/httpdocs/.env
chmod 0600 /srv/openproject/httpdocs/.env

Der Login-Name des initialen OpenProject-Administrators bleibt in vielen Setups admin. Name, E-Mail und Startpasswort werden über die Seed-Variablen gesetzt. Das Passwort sollte beim ersten Login geändert werden.


10. Compose-Datei erstellen

Die Compose-Datei trennt die OpenProject-Dienste in Web, Worker, Cron und Seeder. Dazu kommen PostgreSQL, Memcached und optional Hocuspocus.

containerfile
name: openproject
networks:
backend:
x-op-restart-policy: &restart_policy
restart: unless-stopped
x-op-image: &image
image: "docker.io/openproject/openproject:${TAG:-17-slim}"
x-op-app: &app
<<: [*image, *restart_policy]
env_file:
- .env
volumes:
- "/var/db/openproject/assets:/var/openproject/assets:Z,U"
networks:
- backend
services:
db:
image: "docker.io/library/postgres:${POSTGRES_VERSION:-17}"
<<: *restart_policy
stop_grace_period: "3s"
volumes:
- "/var/lib/postgresql/openproject:/var/lib/postgresql/data:Z,U"
environment:
POSTGRES_DB: "${POSTGRES_DB:-openproject}"
POSTGRES_USER: "${POSTGRES_USER:-openproject}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
networks:
- backend
cache:
image: "docker.io/library/memcached:1.6"
<<: *restart_policy
networks:
- backend
web:
<<: *app
command: "./docker/prod/web"
hostname: "${OPENPROJECT_HOST__NAME:-localhost:8080}"
ports:
- "127.0.0.1:6000:8080"
depends_on:
- db
- cache
- seeder
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health_checks/default"]
interval: 10s
timeout: 3s
retries: 3
start_period: 30s
worker:
<<: *app
command: "./docker/prod/worker"
depends_on:
- db
- cache
- seeder
cron:
<<: *app
command: "./docker/prod/cron"
depends_on:
- db
- cache
- seeder
seeder:
<<: *app
command: "./docker/prod/seeder"
restart: on-failure
hocuspocus:
image: "docker.io/openproject/hocuspocus:${HOCUSPOCUS_TAG:-17.4.0}"
<<: *restart_policy
ports:
- "127.0.0.1:6001:1234"
environment:
SECRET: "${COLLABORATIVE_SERVER_SECRET}"
OPENPROJECT_URL: "${OPENPROJECT_INTERNAL_URL:-http://web:8080}"
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}"
networks:
- backend

Die Datei wird unter /srv/openproject/httpdocs/docker-compose.yaml gespeichert.

bash
editor /srv/openproject/httpdocs/docker-compose.yaml
chown root:openproject /srv/openproject/httpdocs/docker-compose.yaml
chmod 0644 /srv/openproject/httpdocs/docker-compose.yaml

11. OpenProject manuell starten

Der Stack kann zunächst manuell gestartet werden, bevor ein systemd-Service eingerichtet wird.

bash
cd /srv/openproject/httpdocs
podman-compose --file docker-compose.yaml --env-file .env up -d

Der Status wird mit folgenden Befehlen geprüft:

bash
podman ps
podman-compose --file docker-compose.yaml --env-file .env logs -f web
podman-compose --file docker-compose.yaml --env-file .env logs -f seeder

Beim ersten Start initialisiert der Seeder die Datenbank. Je nach Systemleistung kann dieser Schritt einige Minuten dauern.

12. systemd-Service einrichten

Für den produktiven Betrieb sollte der Compose-Stack über systemd gestartet werden.

bash
[Unit]
Description=OpenProject Podman Compose stack
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/openproject/httpdocs
TimeoutStartSec=900
TimeoutStopSec=240
ExecStart=/usr/bin/podman-compose --file /srv/openproject/httpdocs/docker-compose.yaml --env-file /srv/openproject/httpdocs/.env up -d --remove-orphans
ExecStop=/usr/bin/podman-compose --file /srv/openproject/httpdocs/docker-compose.yaml --env-file /srv/openproject/httpdocs/.env down
ExecReload=/usr/bin/podman-compose --file /srv/openproject/httpdocs/docker-compose.yaml --env-file /srv/openproject/httpdocs/.env up -d --remove-orphans
[Install]
WantedBy=multi-user.target
bash
editor /etc/systemd/system/openproject.service
systemctl daemon-reload
systemctl enable --now openproject
systemctl status openproject --no-pager

13. Nginx Reverse Proxy konfigurieren

OpenProject selbst bleibt nur lokal erreichbar. Nginx übernimmt HTTPS, Weiterleitung, Proxy-Header, Upload-Limits und optional WebSocket-Proxying für Hocuspocu

bash
upstream backend.projects.example.org {
server 127.0.0.1:6000;
keepalive 64;
}
upstream hocuspocus.projects.example.org {
server 127.0.0.1:6001;
keepalive 16;
}
server {
listen 80;
listen [::]:80;
server_name projects.example.org;
client_max_body_size 128m;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name projects.example.org;
server_tokens off;
client_max_body_size 128m;
ssl_certificate /etc/letsencrypt/live/projects.example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/projects.example.org/privkey.pem;
location /hocuspocus {
proxy_http_version 1.1;
proxy_buffering off;
proxy_request_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Ssl on;
proxy_redirect off;
proxy_pass http://hocuspocus.projects.example.org;
}
location / {
proxy_http_version 1.1;
proxy_buffering off;
proxy_request_buffering off;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Ssl on;
proxy_redirect off;
proxy_pass http://backend.projects.example.org;
}
}
bash
editor /etc/nginx/sites-available/projects.example.org
ln -sfn /etc/nginx/sites-available/projects.example.org /etc/nginx/sites-enabled/projects.example.org
nginx -t
systemctl reload nginx

14. TLS aktivieren

Sobald DNS korrekt auf den Server zeigt, kann das Zertifikat ausgestellt werden.

bash
certbot --nginx \
-d projects.example.org \
--redirect \
--agree-tos \
--email admin@example.org

Anschließend wird geprüft, ob OpenProject über HTTPS antwortet.

bash
curl -I https://projects.example.org/


15. Erstzugriff und Administrator

Nach dem Start ist OpenProject über die öffentliche URL erreichbar:

bash
https://projects.example.org

Beim ersten Login wird der initiale Administrator verwendet. In vielen Setups ist der Login-Name admin. Das Startpasswort liegt in der .env-Datei als OPENPROJECT_SEED__ADMIN__USER__PASSWORD.

Für den Artikel können folgende Beispieldaten verwendet werden:

  • Administratorin: Anna Berger
  • E-Mail: anna.berger@example.org
  • Projektleiter: Max Schneider
  • E-Mail: max.schneider@example.org
  • Beispielprojekt: Intranet-Relaunch

Screenshots:





16. Beispielprojekt anlegen

Nach der technischen Installation sollte ein Beispielprojekt angelegt werden, um Work Packages, Mitglieder und Rollen zu testen.

Beispielstruktur:

bash
Intranet-Relaunch
├── Konzept
├── Design
├── Umsetzung
├── Tests
└── Go-live

Beispielrollen:

  • Anna Berger: Systemadministratorin
  • Max Schneider: Projektleiter
  • Lena Fischer: Redakteurin
  • David Wagner: Entwickler

Damit kann geprüft werden, ob Projekte, Work Packages, Rollen, Benachrichtigungen und Berechtigungen funktionieren.

17. Backups

Für produktive Umgebungen müssen mindestens drei Bereiche gesichert werden:

  • PostgreSQL-Datenbank
  • OpenProject-Assets
  • .env-Datei mit Secrets

Ein einfaches Datenbankbackup kann so erstellt werden:

bash
cd /srv/openproject/httpdocs
podman-compose --file docker-compose.yaml --env-file .env exec -T db \
pg_dump -U openproject openproject > /var/db/openproject/backups/openproject.sql

Assets und Konfiguration werden separat gesichert:

bash
tar -czf /var/db/openproject/backups/openproject-assets.tar.gz /var/db/openproject/assets
cp /srv/openproject/httpdocs/.env /var/db/openproject/backups/openproject.env
chmod 0600 /var/db/openproject/backups/openproject.env

Backups sollten regelmäßig automatisiert und Wiederherstellungen getestet werden.

18. Updates

Für Updates wird das Image-Tag bewusst angepasst. Produktive Systeme sollten keine unkontrollierten Floating-Tags verwenden.

bash
cd /srv/openproject/httpdocs
sed -i 's/^TAG=.*/TAG=17-slim/' .env
podman-compose --file docker-compose.yaml --env-file .env pull
systemctl reload openproject

Vor Updates sollten Datenbank und Assets gesichert werden. Außerdem sollte geprüft werden, ob Release Notes migrationsrelevante Hinweise enthalten.

19. Best Practices im produktiven Betrieb

Für stabile OpenProject-Installationen haben sich folgende Grundsätze bewährt:

  • feste Image-Tags verwenden
  • PostgreSQL-Daten und Assets persistent speichern
  • .env mit 0600 schützen
  • OpenProject nur lokal binden
  • öffentlichen Zugriff ausschließlich über Nginx und TLS erlauben
  • Backups regelmäßig testen
  • Worker, Cron und Web getrennt beobachten
  • Hocuspocus nur aktivieren, wenn kollaborative Bearbeitung benötigt wird
  • SMTP früh konfigurieren
  • Login- und Passwort-Endpunkte rate-limiten
  • Updates zuerst in einer Testumgebung prüfen

20. Paketinstallation als Alternative

Die paketbasierte Installation mit DEB oder RPM bleibt für bestimmte Distributionen verfügbar. Sie eignet sich vor allem für dedizierte Server, auf denen OpenProject weitgehend allein betrieben wird und der OpenProject-Installer Webserver, Datenbank und Konfiguration verwalten darf.

Für neue Setups sollte diese Variante jedoch bewusst abgewogen werden. OpenProject stellt klar, dass für neuere Linux-Versionen keine neuen Paket-Repositories geplant sind und dass bestimmte zukünftige Funktionen nur Container-basiert ausgeliefert werden sollen.

Wer langfristig flexibel bleiben möchte, sollte deshalb Docker Compose, Podman Compose oder Kubernetes mit Helm als Zielarchitektur betrachten.

21. Fazit

OpenProject lässt sich als selbst gehostete Projektmanagement- und Kollaborationsplattform sauber betreiben, wenn die Architektur klar getrennt wird. Ein Compose-Stack mit Web, Worker, Cron, Seeder, PostgreSQL, Memcached und optional Hocuspocus bildet eine transparente Grundlage für produktive Umgebungen.

Nginx übernimmt den öffentlichen HTTPS-Zugriff, während die Container nur lokal oder intern erreichbar sind. Persistente Daten liegen außerhalb der Container, Secrets werden geschützt in einer .env-Datei verwaltet und Updates erfolgen über bewusst gesetzte Image-Tags.

Damit entsteht eine langfristig wartbare Open-Source-Alternative für Projektmanagement, Roadmaps, Aufgabensteuerung und Team-Kollaboration.

22. Ausblick

Im nächsten Schritt lässt sich dieses Setup vollständig automatisieren. Eine Ansible-Rolle kann Host-Pakete installieren, Benutzer und Verzeichnisse anlegen, Secrets erzeugen, Compose-Dateien rendern, systemd konfigurieren, Nginx bereitstellen und TLS aktivieren.

Für größere Umgebungen ist außerdem ein Kubernetes-Deployment mit Helm interessant. Damit lassen sich Skalierung, Rollouts, Ressourcenlimits und Plattformintegration deutlich strukturierter abbilden.