Deploy tool for small Swarm clusters

Deploy services
on your own servers.

Write one YAML file for the domain, port, exposure, and region. Luma syncs Cloudflare DNS, Traefik HTTPS routes, and Swarm services while each server joins the cluster locally.

DNS Cloudflare sync TLS Traefik ACME Nodes region labels
$ curl -fsSL https://raw.githubusercontent.com/LiuTianjie/luma/main/scripts/install-luma.sh | sh
Deploy preview status.example.com
01 Manifest status.yaml domain: status.example.com
exposure: cn-edge
port: 80
02 Control Luma Control syncs DNS, TLS, routes, and services from the manifest
Cloudflare DNS status.example.com -> cn-edge
Traefik Edge Host rule + ACME certificate
Swarm Nodes status:80 replicated x2
$ luma deploy status.yaml
ok dns     status.example.com -> cn-edge
ok tls     acme certificate requested
ok route   Host(status.example.com) -> status:80
ok swarm   status replicated x2
Domainstatus.example.com Exposurecn-edge Targetstatus:80
Domain in YAML domain is the service address
DNS in Cloudflare Records point to the selected exposure
HTTPS in Traefik Each Host gets its own certificate
Private images Registry auth stays out of YAML

Have these ready first.

01 Domain

Use real domains for the control API and public services, for example luma.example.com and status.example.com.

02 Cloudflare DNS

Put the DNS zone in Cloudflare and create a token that can read the zone and edit records.

03 Linux Manager

Start with one sudo-capable Linux server. A 2c2g manager is enough for testing if app resources are capped.

04 Tailscale optional

Skip Tailscale for a single public manager. Use it for home nodes, private joins, or tailscale-relay.

What each command updates.

Install CLI on Manager 01 / 06
Installing luma on the manager
Manager / Control CLI installed
Traefik Edge not created
CN Worker not joined
Global Worker not joined
Client not logged in
Domain no service yet deploy reads the manifest
DNS pending deploy writes the record
Certificate pending Traefik requests the certificate
Route and workload pending Traefik forwards to service:port
status.yaml
name: status
image: traefik/whoami:latest
region: cn
domain: status.example.com
port: 80
exposure: cn-edge
replicas: 2
resources:
  limits:
    cpus: "0.50"
    memory: 512M

Luma reads the manifest and updates the deploy path.

  • 01
    Read service entry

    Read domain, port, and exposure to get the address and target port.

  • 02
    Write DNS record

    Write the Cloudflare record so the domain points to the selected entry node.

  • 03
    Create HTTPS route

    Traefik reads the Host rule, requests a certificate, and creates the HTTPS route.

  • 04
    Pull private image if needed

    For private GHCR or custom registries, Luma uses saved registry credentials without writing tokens into the manifest.

  • 05
    Update service

    Update the stack so status.example.com forwards to status:80.

  • 06
    Limit resources

    Use Swarm resource limits when apps and control services share a small server.

Run the command for each machine role.

Install the CLI

Install luma on the manager first. Workers and clients install the same command later when needed.

curl -fsSL https://raw.githubusercontent.com/LiuTianjie/luma/main/scripts/install-luma.sh | sh
~/.local/bin/luma preflight

Initialize the control plane

Initialize Swarm on the first server, then start Traefik, Portainer, Luma Control, and the egress gateway.

luma bootstrap manager --domain luma.example.com

Join workload nodes

Each server runs join locally and gets a region label. Scheduling mainly follows region.

luma node join https://luma.example.com --token <join-token> --region global --name global-sg-1

Save private registry credentials

Use saved credentials when the image registry is private.

luma login https://luma.example.com --token <deploy-token>
printf '%s' "$GHCR_TOKEN" | luma registry login ghcr.io --username <user> --password-stdin

Deploy from a client

After login, the client submits the manifest. Luma prints DNS, certificate, route, and stack progress.

luma login https://luma.example.com --token <deploy-token>
luma deploy status.yaml