self-hostingtutorialdocker

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.

Hauke Jung
|April 05, 2026|
3 min read

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.

yaml
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: ["valkey-server", "--requirepass", "${REDIS_PASSWORD}"]
    volumes:
      - valkey_data:/data

volumes:
  mongo_data:
  valkey_data:

Generating secrets

infrapage needs three secrets in a .env file next to the compose file:

sh
# .env
MONGO_ROOT_PASSWORD=$(openssl rand -base64 24)
REDIS_PASSWORD=$(openssl rand -base64 24)
ENCRYPTION_KEY=$(openssl rand -base64 32)
MONGO_KEYFILE_BASE64=$(openssl rand -base64 756 | tr -d '\n')

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:

sh
docker compose exec mongo mongosh -u root -p "$MONGO_ROOT_PASSWORD" \
  --authenticationDatabase admin \
  --eval 'rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "mongo:27017" }] })'

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:

yaml
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.