Skip to content

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:

ContainerRole
mezzanine-frontendNext.js panel UI (served at your panel domain, e.g. app.example.com)
mezzanine-backendExpress API + better-sqlite3 (all state in one SQLite DB)
mezzanine-nginxTLS 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:

  1. Install Docker + the Docker Compose plugin.
  2. Clone the repo to /opt/mezzanine.
  3. Set the panel domain + admin password in deploy/docker/.env.
  4. cd deploy/docker && docker compose up -d --build.
  5. Point your panel domain’s DNS at the box and let nginx/Let’s Encrypt issue the cert.

📷 Screenshot — docker compose ps showing 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:

NavWhat it’s for
OverviewLanding cube/summary of everything connected
DashboardThe “command center” — KPIs, recent activity, health across servers
CloudConnect cloud + Git providers; manage their resources (the heart of the panel)
ServersYour managed boxes (adopt, SSH tools, deploy keys, metrics)
DockerContainers, images, networks across servers
DomainsReverse-proxy vhosts + SSL on your servers
DeployDeploy workflows (CI/CD) + applications
PoolsGroup servers behind a load balancer
TopologyVisual 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.

  1. Cloud → Add provider.
  2. Pick a provider:
    • Cloud hosts — Hostinger, Contabo, Hetzner, DigitalOcean.
    • DNS — Cloudflare (and Hostinger DNS).
    • Source & Git — GitHub, Gitea.
  3. Paste the provider’s API token (create it in that provider’s dashboard — Mezzanine never asks for your password).
  4. 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).

  1. Servers → Add server → host, user (usually root), and auth (password or private key).
  2. Mezzanine verifies the SSH connection and starts collecting metrics.
  3. 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

  1. Deploy → New deploy.
  2. Source — Git (pick from a connected account with the repo browser), an upload, a Docker image, or a compose file.
  3. 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.
  4. Configure — target server, deploy path, ports, env vars. For a private repo, click Add deploy key to authorise the server in one click.
  5. 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).
  6. 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 chipEnable CI/CDGenerate webhook URLAdd 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

  1. Domains → pick the target server at the top.
  2. 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).
  3. Set the target port (the app’s container/host port), kind proxySave.
  4. 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

TaskHow
Where state livesSQLite DB on the backend’s /data volume. Back it up.
SecretsOnly in deploy/docker/.env on the box — never in the repo.
Update the panelPull new code to /opt/mezzanine, docker compose build, docker compose up -d.
Logsdocker compose logs -f backend / frontend.
Static sitesLive under /var/www/<domain>/, mounted read-only into nginx.

📷 Screenshot — terminal: docker compose build && up -d