Skip to content
RolloutDocs
NL
In depth

Self-hosting (Docker)

Prefer your own environment? Rollout runs as a Docker container behind Caddy, with the same code as the hosted version. Administration can stay entirely internal; only booking links and the client portal need to be reachable from outside.

One honest constraint

Rollout checks Microsoft 365, which lives in Microsoft's cloud. So the server must always be able to reach login.microsoftonline.com and graph.microsoft.com. What is possible: no inbound traffic to the admin interface, all outbound traffic through your proxy, and a license check without internet.

One or two addresses

One addressTwo addresses (recommended)
Filedeploy/Caddyfiledeploy/Caddyfile.split
PublicBooking links, portal, consent and the public gatewaybooking.yourmsp.com: only those pages, everything else 404
AdminSame address, only from ADMIN_ALLOWED_RANGESrollout.internal.yourmsp.com: internal or VPN only
Two addresses
  1. booking.yourmsp.com
    internet: booking links and portal only
  2. Caddy
    HTTPS, everything else 404
  3. Rollout
    container, read-only, not root
  4. rollout.internal…
    office or VPN only

Step by step

  1. Database

    Supabase Cloud (EU) or self-hosted. Migrations and the first administrator:

    Terminal
    $ DATABASE_URL='postgresql://postgres:<password>@<db-host>:5432/postgres' ./deploy/migrate.sh
    Klaar: 17 nieuwe migratie(s).
    $ psql "$DATABASE_URL" -c "update platform_settings set bootstrap_admin_email = 'you@yourmsp.com';"
    UPDATE 1
  2. Configuration and secrets
    Terminal
    $ cp deploy/.env.example deploy/.env
    $ install -m 600 rollout-graph.key deploy/secrets/graph-cert-key.pem
    $ openssl rand -hex 32 > deploy/secrets/updater-secret
    $ node scripts/gateway-secret.mjs

    Fill in deploy/.env (ROLLOUT_DOMAIN, APP_URL, Supabase, Graph, ROLLOUT_GATEWAY_SECRET). The Graph certificate is a Docker secret: it never appears in an environment variable, an image or a log.

  3. Two addresses (optional)
    .env
    CADDYFILE=Caddyfile.split
    PUBLIC_DOMAIN=booking.yourmsp.com
    PUBLIC_URL=https://booking.yourmsp.com
    ROLLOUT_DOMAIN=rollout.internal.yourmsp.com
    ADMIN_TLS=internal
    ADMIN_ALLOWED_RANGES=10.0.0.0/8 192.168.0.0/16
    

    In Entra, add https://booking.yourmsp.com/toestemming as a redirect URI of Rollout – Readiness.

  4. Build and start
    Terminal
    $ docker compose -f deploy/compose.yaml --env-file deploy/.env up -d --build
    $ docker compose -f deploy/compose.yaml ps
    rollout running (healthy)
    caddy running
    $ curl -fsS https://rollout.internal.yourmsp.com/api/ready
    {"ready":true,"checks":{"database":"ok","graph":"ok","gateway":"not-enforced","license":"valid"}}

    Then put the hash of the server gateway in the database.

Restrict the admin interface

By default ADMIN_ALLOWED_RANGES is the whole world (0.0.0.0/0). Put your office and VPN IP ranges here, or use two addresses.

Outbound traffic

ToWhy
login.microsoftonline.com:443Tokens for client tenants
graph.microsoft.com:443Readiness, imports, email
Your Supabase address :443Verifying sessions, shared limits
onboarding-planning.vercel.app:443 (optional)Signed list of new versions; turn off with ROLLOUT_UPDATE_CHECK=off

Nothing else: no telemetry, no license server. Behind a proxy: HTTPS_PROXY, NO_PROXY and, with TLS inspection, NODE_EXTRA_CA_CERTS pointing to your own root CA. Never turn off certificate validation.

How the container is hardened

  • Read-only file system, no Linux capabilities (cap_drop: ALL), no privilege escalation, doesn't run as root.
  • No ports to the outside: only Caddy talks to the app.
  • Base image pinned to a fixed version (digest), so the same build gives the same result.
  • The optional updater container only installs versions from the list we sign, and rolls back by itself if a new version doesn't start healthy.
Self-hosting (Docker) · Rollout Docs