Install on a VPS

One curl command on any Linux box. Auto-installs Docker, ships a built-in Caddy reverse proxy, lets you attach a custom domain from the dashboard.

Prerequisites

  • A Linux VPS (Ubuntu 22.04+ recommended; anything with a modern kernel works).
  • Root or sudo access — the installer writes to /opt/maillayer and starts Docker containers.
  • Ports 80, 443, and 8024 free on the host. (Skip the Caddy sidecar with MAILLAYER_NO_CADDY=1 if you bring your own reverse proxy — see below.)

You don't need to install Docker first. If Docker isn't already on the box, the script runs Docker Inc's official installer (get.docker.com) for you. Skip this with MAILLAYER_NO_AUTO_DOCKER=1 if you manage Docker yourself.

Install

SSH into the box and run:

curl -fsSL https://install.maillayer.com/install.sh | sudo bash

The script does six things, in order:

  1. Auto-installs Docker + Compose v2 if they aren't already present. Waits for any in-progress apt processes (cloud-init, unattended-upgrades) to finish first — fresh VPSes commonly have those running for the first 1–3 min after boot.
  2. Checks the required ports are free. Fails fast with a clear remediation hint if 80, 443, or 8024 is already taken.
  3. Generates a random AUTH_SECRET and saves it at /opt/maillayer/.env (mode 600). Re-runs preserve this value — rotating it would invalidate every encrypted credential in the database.
  4. Writes docker-compose.yml with two services: caddy on the public 80 + 443 (handles HTTPS) and maillayer on host port 8024 (the dashboard).
  5. Pulls the images and starts both containers with named volumes for the SQLite database and Caddy's cert state.
  6. Polls /api/health until it returns 200, then prints the dashboard URL.

Total time: ~30s on a box that already has Docker, ~90s if Docker is auto-installed.

Prefer to read the script first?
curl -fsSL https://install.maillayer.com/install.sh -o install.sh
less install.sh
sudo bash install.sh

First sign-in

Open http://<your-server-ip>:8024 in a browser. The first account that signs up becomes the install owner. From there you'll create your first brand, configure an email provider, and (optionally) attach a custom domain — all from the dashboard.

Add a custom domain (built-in HTTPS)

The bundled Caddy sidecar is wired up at install time but stays inert until you give it a domain. To attach one:

  1. Point an A record for your domain at this server's public IP. (If you use Cloudflare, set the proxy to DNS only — gray cloud — until the cert issues. You can re-enable orange-cloud proxy afterwards with SSL mode set to "Full (strict)".)
  2. In the dashboard, go to Settings → Domain. Enter your domain and a contact email.
  3. Click Save & request certificate. Caddy issues a Let's Encrypt cert in the background (typically 30–60s). The status pill flips from Issuing cert… to Live once it's done.

Certificates are auto-renewed by Caddy — there's nothing to renew on your side. See Custom domain for the full flow including common DNS gotchas.

Override install defaults

Pass env vars before the curl command:

MAILLAYER_PORT=8080 \
MAILLAYER_DIR=/var/lib/maillayer \
  curl -fsSL https://install.maillayer.com/install.sh | sudo -E bash
Env varDefaultWhat it does
MAILLAYER_DIR/opt/maillayerInstall root.
MAILLAYER_PORT8024Local-IP dashboard port (always exposed for SSH-tunnel access).
MAILLAYER_URL(empty)Public URL — set this if you proxy externally and aren't using the bundled Caddy.
MAILLAYER_IMAGEghcr.io/mddanishyusuf/maillayer-pro:1Image tag. Pin to a specific version like :v1.2.4 to lock.
MAILLAYER_NO_CADDY0Set to 1 to skip the bundled Caddy — bring your own reverse proxy.
MAILLAYER_NO_AUTO_DOCKER0Set to 1 to disable the Docker auto-install.

Bring your own reverse proxy

Already running nginx, Traefik, or Cloudflare Tunnel on the box? Skip the bundled Caddy sidecar:

MAILLAYER_NO_CADDY=1 \
  curl -fsSL https://install.maillayer.com/install.sh | sudo -E bash

The installer writes a single-service compose file (only maillayer, no Caddy) and exposes port 8024 on the host. Point your existing proxy at localhost:8024 and set APP_URL in /opt/maillayer/.env to your public URL so tracking links resolve correctly. The Settings → Domain page will render a "Managed externally" notice instead of the cert-request form.

Quick nginx example
server {
  listen 443 ssl;
  server_name mail.example.com;
  ssl_certificate     /etc/letsencrypt/live/mail.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/mail.example.com/privkey.pem;
  location / {
    proxy_pass http://127.0.0.1:8024;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Day-to-day

  • Logs (everything): docker compose -f /opt/maillayer/docker-compose.yml logs -f
  • Logs (just one service): docker compose -f /opt/maillayer/docker-compose.yml logs -f maillayer
  • Update to latest 1.x: cd /opt/maillayer && docker compose pull && docker compose up -d
  • Re-run installer (also pulls latest): curl -fsSL https://install.maillayer.com/install.sh | sudo bash — detects an existing install, preserves your AUTH_SECRET, and force-recreates containers so config changes take effect.
  • Stop: cd /opt/maillayer && docker compose down (data persists in volumes)
  • Backup: see Backups — TL;DR is to copy /opt/maillayer/.env + the SQLite volume.

Pinning to a specific version

The default :1 tag is rolling — it always points at the latest 1.x release. To pin a specific version (e.g. for a production install where you want explicit upgrades):

MAILLAYER_IMAGE=ghcr.io/mddanishyusuf/maillayer-pro:v1.2.4 \
  curl -fsSL https://install.maillayer.com/install.sh | sudo -E bash

After install, edit /opt/maillayer/docker-compose.yml and bump the image: line whenever you want to upgrade.

Uninstall

cd /opt/maillayer && docker compose down -v
sudo rm -rf /opt/maillayer

down -v removes the named volumes too, so this is destructive — your SQLite database and Caddy's cert state both go away. Back up first if you want to keep anything.