Self-hosting infrapage with Docker
A step-by-step walkthrough of running infrapage on your own server with Docker Compose, MongoDB, and Valkey. No Kubernetes required.
infrapage is designed to self-host. One Rust binary, one Mongo replica, one optional Valkey, a reverse proxy — that's the whole stack. This post walks through a minimal setup you can copy onto any VPS with Docker installed.
What you need
- A server with Docker and Docker Compose (a 1 GB VPS is plenty)
- A domain pointed at the server
- About 10 minutes
The compose file
Start with a docker-compose.yml that defines three services: the infrapage app, MongoDB (as a single-node replica set so transactions work), and Valkey.
services:
infrapage:
image: ghcr.io/hauju/infrapage:latest
restart: always
ports:
- "8080:8080"
environment:
MONGODB_URI: mongodb://root:${MONGO_ROOT_PASSWORD}@mongo:27017/infrapage?authSource=admin&replicaSet=rs0
VALKEY_URI: redis://:${REDIS_PASSWORD}@valkey:6379
BASE_URL: https://dashboard.example.com
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
ALLOW_OPEN_ACCESS: "true"
depends_on:
- mongo
- valkey
mongo:
image: mongo:7
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
command: >
bash -lc '
echo "$$MONGO_KEYFILE_BASE64" | base64 -d > /data/keyfile
chmod 600 /data/keyfile && chown 999:999 /data/keyfile
exec mongod --replSet rs0 --bind_ip_all --auth --keyFile /data/keyfile
'
volumes:
- mongo_data:/data/db
valkey:
image: valkey/valkey:latest
restart: always
command:
volumes:
- valkey_data:/data
volumes:
mongo_data:
valkey_data:Generating secrets
infrapage needs three secrets in a .env file next to the compose file:
# .env
MONGO_ROOT_PASSWORD=
REDIS_PASSWORD=
ENCRYPTION_KEY=
MONGO_KEYFILE_BASE64=The ENCRYPTION_KEY encrypts integration tokens at rest. Lose it and stored tokens become unrecoverable — back it up.
Initializing the replica set
MongoDB needs one-time replica set initialization. The first time you start the stack, exec into the mongo container and run:
You'll see { ok: 1 }. If you see "already initialized" — good, you're already past this step.
Reverse proxy
infrapage listens on plain HTTP. For a production deployment, put Caddy or Nginx in front to handle TLS. A one-line Caddyfile is enough:
dashboard.example.com {
reverse_proxy infrapage:8080
}Set SECURE_COOKIES=true in the app's environment once you're on HTTPS, and point BASE_URL at the public URL.
Enabling auth
By default the stack runs with ALLOW_OPEN_ACCESS=true — every page is editable by anyone who can reach the URL. That's fine on a private network, catastrophic on the open internet.
For real deployments, set up Zitadel as the identity provider, then add:
environment:
ZITADEL_DOMAIN: auth.example.com
ZITADEL_CLIENT_ID: 284...
ZITADEL_REDIRECT_URI: https://dashboard.example.com/auth/callback
ALLOW_OPEN_ACCESS: "false"Zitadel is itself self-hostable, which keeps the whole stack independent of third-party identity providers.
What you get
After docker compose up -d, you have:
/— the landing page and dashboard editor/p/your-page-slug— public dashboard pages (SSR + hydration, cacheable)/docs— the documentation shipped with the binary/blog— this blog/sitemap.xml,/robots.txt,/llms.txt— SEO and LLM discoverability
The full configuration reference lives in the self-hosting docs. If something breaks during setup, open an issue on the GitHub repo — that's the fastest path to getting it fixed for everyone.