Copy-paste commands for operating the REMICO platform. Passwords and secrets come from the respective .env files — substitute <...> placeholders.
External networks must exist before starting any compose stack:
docker network create frontend 2>/dev/null || true
docker network create backend 2>/dev/null || true
docker network ls | grep -E 'frontend|backend'
If a container fails with network frontend declared as external, but could not be found, run the commands above.
Each service has its own directory under /opt/platform/:
| Stack | Path |
|---|---|
| Traefik | /opt/platform/traefik |
| PostgreSQL | /opt/platform/postgres |
| Keycloak + oauth2-proxy | /opt/platform/keycloak |
| auth-bounce | /opt/platform/auth-bounce |
| Wiki.js | /opt/platform/wiki |
| Gitea | /opt/platform/gitea |
| Dashboard (Dashy) | /opt/platform/dashboard |
| Monitoring | /opt/platform/monitoring |
| API | /opt/platform/api |
| Web | /opt/platform/web |
| Analytics (Umami) | /opt/platform/analytics |
Common operations (run from the stack directory):
cd /opt/platform/<stack>
# Start or recreate changed containers
docker compose up -d
# Stop containers (volumes preserved)
docker compose down
# Pull latest images and recreate
docker compose pull
docker compose up -d
# Follow logs
docker compose logs -f
# Logs for a single container
docker logs -f <container-name>
Recommended start order:
# 1. Networks (once)
docker network create frontend 2>/dev/null; docker network create backend 2>/dev/null
# 2. Database
cd /opt/platform/postgres && docker compose up -d
# 3. Identity
cd /opt/platform/keycloak && docker compose up -d
# 4. Edge proxy
cd /opt/platform/traefik && docker compose up -d
# 5. Auth helper
cd /opt/platform/auth-bounce && docker compose up -d
# 6. Applications
cd /opt/platform/wiki && docker compose up -d
cd /opt/platform/gitea && docker compose up -d
cd /opt/platform/analytics && ./setup-analytics.sh
# 7. Everything else
cd /opt/platform/dashboard && docker compose up -d
cd /opt/platform/monitoring && docker compose up -d
cd /opt/platform/api && docker compose up -d
cd /opt/platform/web && docker compose up -d
Check all containers:
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
Restart a single container without touching compose:
docker restart <container-name>
Shared instance — databases: keycloak, wiki, gitea, umami.
# Interactive psql as superuser
docker exec -it postgres psql -U admin -d postgres
# List databases
docker exec postgres psql -U admin -d postgres -c '\l'
# List roles
docker exec postgres psql -U admin -d postgres -c '\du'
Password from /opt/platform/wiki/.env → WIKI_DB_PASS:
docker exec postgres psql -U admin -d postgres -c \
"CREATE USER wikijs WITH PASSWORD '<WIKI_DB_PASS>';"
docker exec postgres psql -U admin -d postgres -c \
"CREATE DATABASE wiki OWNER wikijs;"
# If user already exists — update password only:
docker exec postgres psql -U admin -d postgres -c \
"ALTER USER wikijs WITH PASSWORD '<WIKI_DB_PASS>';"
Password from /opt/platform/gitea/.env → GITEA_DB_PASS:
docker exec postgres psql -U admin -d postgres -c \
"CREATE USER gitea WITH PASSWORD '<GITEA_DB_PASS>';"
docker exec postgres psql -U admin -d postgres -c \
"CREATE DATABASE gitea OWNER gitea;"
docker exec postgres psql -U admin -d postgres -c \
"GRANT ALL PRIVILEGES ON DATABASE gitea TO gitea;"
Password from /opt/platform/analytics/.env → UMAMI_DB_PASSWORD:
/opt/platform/analytics/setup-analytics.sh
Or manually:
docker exec postgres psql -U admin -d postgres -c \
"CREATE USER umami WITH PASSWORD '<UMAMI_DB_PASSWORD>';"
docker exec postgres psql -U admin -d postgres -c \
"CREATE DATABASE umami OWNER umami;"
# List wiki users
docker exec postgres psql -U wikijs -d wiki -c \
"SELECT id, email, name FROM users;"
# Check group membership (Administrators = groupId 1)
docker exec postgres psql -U wikijs -d wiki -c \
"SELECT u.email, g.name FROM users u
JOIN \"userGroups\" ug ON u.id = ug.\"userId\"
JOIN groups g ON g.id = ug.\"groupId\";"
# Add user to Administrators manually (replace <uid>)
docker exec postgres psql -U wikijs -d wiki -c \
"INSERT INTO \"userGroups\" (\"userId\", \"groupId\")
SELECT <uid>, 1
WHERE NOT EXISTS (
SELECT 1 FROM \"userGroups\" WHERE \"userId\" = <uid> AND \"groupId\" = 1
);"
# Force re-login (clear wiki sessions)
docker exec postgres psql -U wikijs -d wiki -c 'DELETE FROM sessions;'
docker restart wiki
Admin Console: https://auth.remico.eu/admin
docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \
--server http://127.0.0.1:8080 \
--realm master \
--user webmaster@remico.eu \
--password '<admin password>'
All following kcadm commands run inside the container with -r remico for the application realm.
# List realm users
docker exec keycloak /opt/keycloak/bin/kcadm.sh get users -r remico \
--fields username,email,enabled
# Create a user
docker exec keycloak /opt/keycloak/bin/kcadm.sh create users -r remico \
-s username=user@remico.eu \
-s email=user@remico.eu \
-s enabled=true \
-s emailVerified=true
# Set password
UID=$(docker exec keycloak /opt/keycloak/bin/kcadm.sh get users -r remico \
-q username=user@remico.eu --fields id --format csv --noquotes | tail -1)
docker exec keycloak /opt/keycloak/bin/kcadm.sh set-password -r remico \
--userid "$UID" --new-password '<password>'
# List clients
docker exec keycloak /opt/keycloak/bin/kcadm.sh get clients -r remico \
--fields clientId,enabled
# Get internal client UUID (needed for updates)
CID=$(docker exec keycloak /opt/keycloak/bin/kcadm.sh get clients -r remico \
-q clientId=gitea --fields id --format csv --noquotes | tail -1)
echo "Client ID: $CID"
# Update redirect URIs (example: gitea)
docker exec keycloak /opt/keycloak/bin/kcadm.sh update "clients/${CID}" -r remico \
-s 'redirectUris=["https://gitea.remico.eu/user/oauth2/Keycloak/callback"]' \
-s 'webOrigins=["https://gitea.remico.eu"]'
# Get client secret — always use --fields value to avoid parsing artifacts
docker exec keycloak /opt/keycloak/bin/kcadm.sh get "clients/${CID}/client-secret" -r remico \
--fields value --format csv --noquotes
# Regenerate secret
docker exec keycloak /opt/keycloak/bin/kcadm.sh create "clients/${CID}/client-secret" -r remico
Required for Gitea RP-initiated logout:
docker exec keycloak /opt/keycloak/bin/kcadm.sh update "clients/${CID}" -r remico \
-s 'attributes."post.logout.redirect.uris"=https://gitea.remico.eu/##https://gitea.remico.eu/user/login##https://gitea.remico.eu/*'
cd /opt/platform/keycloak && docker compose up -d
docker logs keycloak 2>&1 | tail -30
# List users
docker exec gitea gitea admin user list
# Promote user to admin
docker exec gitea gitea admin user modify --username <name> --admin
# List OAuth sources
docker exec gitea gitea admin auth list
# Update OAuth source after Keycloak secret rotation
# Get secret first (see Keycloak section), then:
docker exec gitea gitea admin auth update-oauth --id 1 \
--provider openidConnect \
--name Keycloak \
--key gitea \
--secret '<client secret from Keycloak>' \
--auto-discover-url https://auth.remico.eu/realms/remico/.well-known/openid-configuration \
--scopes openid --scopes email --scopes profile
# Verify OAuth config in database
docker exec postgres psql -U gitea -d gitea -c \
"SELECT id, name, cfg FROM login_source;"
# OAuth / logout logs
docker logs gitea 2>&1 | grep -iE 'oauth|logout|error|callback' | tail -30
After changing compose env vars (GITEA__*):
cd /opt/platform/gitea && docker compose up -d
Wiki.js exposes GraphQL at http://wiki:3000/graphql (internal network only).
# List pages (no auth required for some queries; admin JWT for mutations)
docker run --rm --network frontend curlimages/curl:latest -s \
http://wiki:3000/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ pages { list { id path title } } }"}'
# Obtain JWT
docker run --rm --network frontend curlimages/curl:latest -s \
http://wiki:3000/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"mutation { authentication { login(username: \"<admin email>\", password: \"<admin password>\", strategy: \"local\") { jwt } } }"}'
Use the returned JWT as Authorization: Bearer <jwt> for authenticated mutations.
If a Keycloak user logged in via SSO but lacks wiki admin rights:
# 1. Find their wiki user ID
docker exec postgres psql -U wikijs -d wiki -c \
"SELECT id, email FROM users WHERE email ILIKE '%user@remico.eu%';"
# 2. Add to Administrators (groupId = 1)
docker exec postgres psql -U wikijs -d wiki -c \
"INSERT INTO \"userGroups\" (\"userId\", \"groupId\")
VALUES (<uid>, 1)
ON CONFLICT DO NOTHING;"
# 3. Clear sessions and restart
docker exec postgres psql -U wikijs -d wiki -c 'DELETE FROM sessions;'
docker restart wiki
Users not yet in the wiki DB are created automatically on next SSO login (autoEnrollGroups).
# First-time setup (creates umami DB on shared postgres)
cd /opt/platform/analytics && ./setup-analytics.sh
# Restart stack
cd /opt/platform/analytics && docker compose up -d
# Logs
docker logs umami 2>&1 | tail -30
docker logs umami-sso 2>&1 | tail -20
# DB connectivity
docker exec postgres psql -U umami -d umami -c '\dt' | head -15
# SSO bridge (simulate authenticated request)
docker run --rm --network frontend curlimages/curl:latest -s -o /dev/null -w '%{http_code}' \
-H 'X-Auth-Request-Email: user@remico.eu' http://umami-sso:8080/login
After adding a website in Umami UI, set WEBSITE_ID in /opt/platform/web/html/js/analytics.js.
Dashboard: https://analytics.remico.eu (Keycloak SSO, no Umami login form).
# Recent Traefik logs
docker logs traefik 2>&1 | tail -50
# Certificate store
ls -la /opt/platform/traefik/acme.json
# Verify HTTPS routing
curl -sI https://wiki.remico.eu | head -5
curl -sI https://work.remico.eu | head -5
# Traefik dashboard (requires SSO)
# https://traefik.remico.eu
If certificates fail to renew, check that port 80 is reachable (HTTP challenge) and acme.json is writable.
# Restart monitoring stack
cd /opt/platform/monitoring && docker compose up -d
# Prometheus health
docker exec prometheus wget -qO- http://localhost:9090/-/healthy
# Grafana health (requires SSO cookie — use from browser or check container directly)
docker exec grafana wget -qO- http://localhost:3000/api/health
# Prometheus targets
docker exec prometheus wget -qO- http://localhost:9090/api/v1/targets | python3 -m json.tool | head -40
# Node exporter metrics (internal)
docker exec node-exporter wget -qO- http://localhost:9100/metrics | head -5
DNS for remico.eu is managed via Hetzner Console DNS API (https://dns.hetzner.com/api/v1). Requires an API token from https://dns.hetzner.com/settings/api-token.
export HETZNER_DNS_TOKEN='<your token>'
export ZONE='remico.eu'
export IPV4='178.105.232.95'
export IPV6='2a01:4f8:1c1a:63a4::1'
# Look up zone ID
curl -s "https://dns.hetzner.com/api/v1/zones?name=${ZONE}" \
-H "Auth-API-Token: ${HETZNER_DNS_TOKEN}" | python3 -m json.tool
# Set ZONE_ID from response, then create an A record (example: wiki)
curl -s -X POST "https://dns.hetzner.com/api/v1/records" \
-H "Auth-API-Token: ${HETZNER_DNS_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"zone_id\": \"<ZONE_ID>\", \"type\": \"A\", \"name\": \"wiki\", \"value\": \"${IPV4}\", \"ttl\": 300}"
# Wildcard (covers all subdomains)
curl -s -X POST "https://dns.hetzner.com/api/v1/records" \
-H "Auth-API-Token: ${HETZNER_DNS_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"zone_id\": \"<ZONE_ID>\", \"type\": \"A\", \"name\": \"*\", \"value\": \"${IPV4}\", \"ttl\": 300}"
Subdomains to record: wiki, work, auth, api, analytics, gitea, grafana, prometheus, traefik, and wildcard *.
Verify propagation:
dig +short wiki.remico.eu A
dig +short wiki.remico.eu @ns1.your-server.de A
# Check status
ufw status verbose
# Allow essential ports
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
# Enable (if not already)
ufw enable
# Docker + UFW: ensure FORWARD chain allows traffic
grep -A5 'DOCKER' /etc/ufw/after.rules
If containers are unreachable after enabling UFW, check that Docker forwarding rules are present in /etc/ufw/after.rules.
# Logs
docker logs oauth2-proxy 2>&1 | tail -30
# Restart (picks up env changes)
cd /opt/platform/keycloak && docker compose up -d oauth2-proxy
# Test forwardAuth endpoint (returns 401 without cookie — expected)
docker run --rm --network frontend curlimages/curl:latest -sI \
http://oauth2-proxy:4180/oauth2/auth | head -5
If remico-web client secret is rotated in Keycloak, update OAUTH2_PROXY_CLIENT_SECRET in /opt/platform/keycloak/docker-compose.yml and recreate oauth2-proxy.
| Symptom | Check |
|---|---|
| Redirect loop on Wiki/Gitea | Traefik labels must NOT include oauth-auth middleware |
| Gitea SSO callback → login page | OAuth secret in Gitea DB (login_source.cfg); scopes must include email; DISABLE_REGISTRATION must be false for OAuth signup |
| Gitea logout → instant re-login | Keycloak SSO session alive; verify KC_SPI_…SUPPRESS_LOGOUT_CONFIRMATION_SCREEN=true on keycloak container |
| Wiki NXDOMAIN | DNS wildcard * missing or wrong A/AAAA record in Hetzner zone |
| Container won't start | docker network create frontend/backend; check docker compose logs |
| oauth2-proxy 401 loop | OAUTH2_PROXY_CLIENT_SECRET mismatch with Keycloak remico-web client |
| Wiki user no admin | userGroups table — add groupId 1 (Administrators) |
| TLS certificate errors | acme.json permissions; port 80 reachable; check docker logs traefik |
| Postgres connection refused | Ensure postgres container is running and on backend network |
| Umami login form / 502 on /login | umami-sso logs; UMAMI_ADMIN_PASS must match Umami admin; umami must reach postgres:5432 |
| No analytics on remico.eu | Set WEBSITE_ID in web/html/js/analytics.js |
# Container health overview
docker ps -a --format 'table {{.Names}}\t{{.Status}}'
# Inspect a container
docker inspect <name> --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool
# Test internal connectivity
docker run --rm --network frontend curlimages/curl:latest -sI http://wiki:3000/
docker run --rm --network backend curlimages/curl:latest -sI http://postgres:5432/ || true
# Disk usage
df -h /
docker system df