App box — self-hosted panel
This is the hands-on tutorial for the App box — a single server running Mezzanine as your own control panel (one operator/team, your infrastructure). If you want the multi-tenant hosted product instead, see the SaaS box guide.
What the App box is
A single VPS running three containers behind Docker:
| Container | Role |
|---|---|
mezzanine-frontend | Next.js panel UI (served at your panel domain, e.g. app.example.com) |
mezzanine-backend | Express API + better-sqlite3 (all state in one SQLite DB) |
mezzanine-nginx | TLS termination + reverse proxy in front of both |
State lives in a single SQLite database on a Docker volume (/data/mezzanine.db) — providers, servers, domains, workflows, users. Static sites you publish live under /var/www/ and are mounted read-only into the nginx container.
📷 Screenshot — the panel right after login (Overview / cube hero)
1. Install
Follow Installation for the full first-boot script. In short, on a fresh Ubuntu VPS:
- Install Docker + the Docker Compose plugin.
- Clone the repo to
/opt/mezzanine. - Set the panel domain + admin password in
deploy/docker/.env. cd deploy/docker && docker compose up -d --build.- Point your panel domain’s DNS at the box and let nginx/Let’s Encrypt issue the cert.
📷 Screenshot —
docker compose psshowing frontend/backend/nginx healthy
First login
Open your panel domain → sign in with the admin credentials from .env. The first account is the owner/admin.
📷 Screenshot — login screen
2. The navigation, at a glance
The top nav is the whole product:
| Nav | What it’s for |
|---|---|
| Overview | Landing cube/summary of everything connected |
| Dashboard | The “command center” — KPIs, recent activity, health across servers |
| Cloud | Connect cloud + Git providers; manage their resources (the heart of the panel) |
| Servers | Your managed boxes (adopt, SSH tools, deploy keys, metrics) |
| Docker | Containers, images, networks across servers |
| Domains | Reverse-proxy vhosts + SSL on your servers |
| Deploy | Deploy workflows (CI/CD) + applications |
| Pools | Group servers behind a load balancer |
| Topology | Visual graph of servers, apps, and routes |
📷 Screenshot — top nav bar
3. Connect providers (Cloud)
Cloud is where you wire in everything Mezzanine talks to. It shows only connected providers, plus an Add provider catalog.
- Cloud → Add provider.
- Pick a provider:
- Cloud hosts — Hostinger, Contabo, Hetzner, DigitalOcean.
- DNS — Cloudflare (and Hostinger DNS).
- Source & Git — GitHub, Gitea.
- Paste the provider’s API token (create it in that provider’s dashboard — Mezzanine never asks for your password).
- The provider tile flips to connected and its resources load.
📷 Screenshot — Cloud page: Add provider catalog 📷 Screenshot — a connected provider tile (e.g. Hostinger) with VMs/domains/billing tabs
Git accounts (multiple)
Under Cloud → Git you can connect several GitHub/Gitea accounts (e.g. a personal account and an org). A chip row lets you switch the active account. A single org-scoped token covers every repo in that org. See Git providers for token scopes.
📷 Screenshot — Git section with the account chip switcher
4. Add a server
A “server” is any box you can SSH to. Servers → Add server, or adopt one straight from a connected cloud provider (Hostinger/Contabo VPS → Adopt to Mezzanine).
- Servers → Add server → host, user (usually
root), and auth (password or private key). - Mezzanine verifies the SSH connection and starts collecting metrics.
- Open the server to find Deploy key (the box’s SSH public key — register it on a repo so it can clone over SSH), cron, logs, firewall, snapshots.
📷 Screenshot — Servers list + a server detail with the Deploy key panel
5. Deploy an app (the core flow)
Everything ships through Deploy → Workflows. A workflow is a reusable recipe whose every step is tracked and replayable.
a. Create a workflow
- Deploy → New deploy.
- Source — Git (pick from a connected account with the repo browser), an upload, a Docker image, or a compose file.
- Destination template — choose how it runs: Static site, Next.js (source), Node/Express, Django/Flask/FastAPI, Docker (any stack), monorepo variants, etc. The wizard auto-detects sensible defaults.
- Configure — target server, deploy path, ports, env vars. For a private repo, click Add deploy key to authorise the server in one click.
- How should it run? — pick the trigger: Manual, Schedule (cron), or On push (webhook). On-push generates a signed URL + secret you add to the repo (one click if it’s a connected repo).
- Create (optionally “run the first deploy now”).
📷 Screenshot — wizard step 1 (source) and the destination template grid 📷 Screenshot — wizard final step: “How should it run?” trigger picker
b. Watch it deploy — the live terminal
Click Run on a workflow card and an inline terminal opens, streaming each stage (Sync code → Build → Run container) as numbered sections, with status, durations, and a Stop button. A history sidebar lets you click any past run to read its log.
📷 Screenshot — the live terminal modal mid-deploy with the history sidebar
c. Auto-deploy on push (CI/CD)
To make pushes deploy themselves, open a workflow’s trigger chip → Enable CI/CD → Generate webhook URL → Add to repo (one click registers it on the connected repo). After that: git push → webhook → workflow runs → app updates. See Deploy workflows.
📷 Screenshot — Enable CI/CD modal with the “Add to repo” button
d. Push without git — Linked folders
In Cloud → Git → a repo, the Linked folder panel lets you wire a local project folder (Chromium browsers) and Push changes straight to the repo — gitignore-filtered, only changed files — which then fires the webhook and deploys. Great for an edit-and-ship flow with no local git. See Git providers.
📷 Screenshot — repo detail: Linked folder panel + Push changes
6. Put it on a domain with HTTPS
- Domains → pick the target server at the top.
- Add Domain → use Pick from a connected DNS provider to choose the provider + base domain, type a subdomain, and Create A record → <server IP> in one click (Cloudflare / Hostinger DNS).
- Set the target port (the app’s container/host port), kind proxy → Save.
- Apply writes the nginx vhost; Enable SSL issues a Let’s Encrypt cert.
📷 Screenshot — Add Domain with the DNS provider picker 📷 Screenshot — a domain row showing HTTPS + healthy
7. Pools + Topology
- Pools group several servers behind a single nginx load balancer so one hostname spreads traffic across them.
- Topology draws servers, apps, containers, and routes as a live graph.
📷 Screenshot — Pools page 📷 Screenshot — Topology graph
8. Settings & users
- Settings → Integrations shows every provider’s live status with connect/manage deep-links.
- Multi-user roles — invite users as admin / moderator / viewer; the UI hides what a role can’t do. See Multi-user roles.
- Notifications — outbound alerts (Slack/Discord/webhook/email) on deploy + health events.
📷 Screenshot — Settings → Integrations
9. Operating the box
| Task | How |
|---|---|
| Where state lives | SQLite DB on the backend’s /data volume. Back it up. |
| Secrets | Only in deploy/docker/.env on the box — never in the repo. |
| Update the panel | Pull new code to /opt/mezzanine, docker compose build, docker compose up -d. |
| Logs | docker compose logs -f backend / frontend. |
| Static sites | Live under /var/www/<domain>/, mounted read-only into nginx. |
📷 Screenshot — terminal:
docker compose build && up -d