← DocumentationHosting

Serving & deploying self-hosted Gantry projects

The deployment model is deliberately progressive: run on localhost first, install a user service when the process should persist, put a reverse proxy in front when you need a hostname/TLS, and only add clustering or distributed nodes when a single machine stops being the right architecture.

1. Start on loopback

For a single-machine installation, keep the service bound to 127.0.0.1. The corresponding my.* launcher already assumes the standard localhost port when no explicit instance is configured.

ServiceDefault local URLLauncher
Cortexhttp://localhost:7331my.crtx.dev
Wardenhttp://localhost:7332my.warden.cv
Trestlehttp://localhost:7333my.trestle.cv
Watchposthttp://localhost:7334my.watchpost.cv
Watchpost Agenthttp://localhost:7335agent.watchpost.cv
Webfleethttp://localhost:7336my.webfleet.cv

Binding to loopback is a feature, not a limitation: it means the application is not accidentally listening on every LAN/public interface while you are still treating it as a local tool.

2. Install the service when it should outlive your shell

Where the project implements the Gantry service API, install it through the project executable rather than backgrounding a foreground process manually. Cortex, for example:

cortex service install --host 127.0.0.1 --port 7331
cortex service status

The user service can start with your systemd user manager and survives the terminal that launched it. On an always-on server, enable user lingering when the service must keep running after logout:

loginctl enable-linger "$USER"

See Service lifecycle API for install/restart/status/logs/uninstall semantics.

3. Give the service a real hostname

For remote access, create DNS for a hostname such as cortex.example.com and put a reverse proxy on the same host or trusted front-end machine. Keep the Gantry application itself on loopback where possible:

Browser
  │ HTTPS
  ▼
cortex.example.com :443
  │ reverse proxy on loopback
  ▼
127.0.0.1:7331

This keeps TLS certificates, HTTP/2/3 and public listener concerns in a purpose-built proxy while the Gantry service stays focused on the application.

Caddy example

Caddy is an excellent fit when you want automatic HTTPS and a very small configuration. Assuming DNS points at the host:

cortex.example.com {
    reverse_proxy 127.0.0.1:7331
}

Then install/reconfigure Cortex with the canonical public origin and proxy trust:

cortex service install \
  --host 127.0.0.1 \
  --port 7331 \
  --public-origin https://cortex.example.com \
  --trust-proxy

Caddy forwards WebSocket upgrades automatically, which is useful for Gantry applications with terminals, realtime updates or long-lived browser connections.

nginx example

For nginx, preserve the original host/scheme and WebSocket upgrade headers. A reusable configuration looks like:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name cortex.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name cortex.example.com;

    ssl_certificate     /etc/letsencrypt/live/cortex.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cortex.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:7331;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

Use your normal ACME/certificate workflow; the important Gantry-specific part is that the app remains loopback-bound and knows the canonical public origin when the project supports that setting.

--trust-proxy is a trust boundary

Do not enable proxy trust merely because the app is behind “something.” On Cortex, forwarded identity/scheme information is accepted only from the direct loopback peer when proxy trust is enabled. Keep the reverse proxy and service relationship explicit; do not expose the backend listener publicly and then trust arbitrary forwarding headers from the internet.

Private network / VPN deployment

You do not need to publish a Gantry service to the internet. A private DNS name over Tailscale, WireGuard, a corporate VPN or a LAN is often the better deployment. You can terminate TLS internally, or let the VPN provide the network trust boundary and still keep application authentication enabled.

Add that private hostname/IP to the appropriate my.* launcher so the same human entry point works across your local, NUC and remote deployments.

Firewalls and exposed ports

If the reverse proxy is on the same machine, expose only the proxy ports (normally 80/443) through the host firewall and keep the Gantry port on loopback. If the proxy is on another machine, bind the Gantry service to the private interface and firewall the port so only the proxy/VPN subnet can reach it.

Persistent data and upgrades

Treat the executable and persistent data as separate concerns where the project supports a data path. Back up the data directory/database, not merely the binary. Before replacing a release, stop or restart through the service API rather than killing an arbitrary PID, and use service status plus the application’s own health/backup guidance after the upgrade.

Trestle deserves extra care because it can hold application data and uploaded files; SQLite and PostgreSQL deployments have different backup procedures. Project-specific docs take precedence over generic Gantry examples.

Multiple instances on one host

Multiple deployments can coexist by using separate ports and, where applicable, separate roots/data directories. Give each a meaningful external hostname or my.* instance name:

Local dev      localhost:7331
Home NUC       cortex.home.example
Production     cortex.prod.example

The my.* launcher is then the human routing layer, while DNS/reverse proxies remain the network routing layer.

Scaling beyond one process

Clustering is not a checkbox to turn on pre-emptively. Trestle, Watchpost and Webfleet have credible high-availability/scale reasons to cluster, while Watchpost and Webfleet additionally benefit from distributed machine/region placement. The single-node service path should stay intact when those modes arrive.

Deployment checklist

  • Bind to loopback unless remote access requires otherwise.
  • Use the project service API for persistent process lifecycle.
  • Use a real hostname and TLS for browser access outside localhost.
  • Set --public-origin and proxy trust only when the project and topology require them.
  • Preserve authentication even on private networks.
  • Back up persistent state separately from the executable.
  • Monitor the host with Watchpost and the public endpoint with Webfleet when the deployment matters.