Skip to main content

Luma

Luma

Your servers. One deployment workflow.
A self-hosted control plane for deploying containers across cloud and home infrastructure.

PyPI version Python 3.9 or newer MIT license

Website · Getting started · Docs · Console guide · Releases · 简体中文


Luma turns a handful of servers into a deployment platform you can operate from your laptop, CI, or browser. Describe a service in YAML, choose where it runs and how it is reached, then deploy through one authenticated API.

Underneath, Nomad schedules containers, Traefik routes traffic, and Cloudflare manages DNS. Luma connects those pieces with application history, secrets, builds, node management, and a web console. Deployment clients carry a management token; infrastructure credentials stay with the control plane.

# status.yaml
name: status
image: traefik/whoami:v1.10.3
region: cn
exposure: cn-edge
domain: status.example.com
port: 80
luma validate status.yaml
luma deploy status.yaml --dry-run
luma deploy status.yaml

The example assumes an initialized manager and a domain in your Cloudflare zone. Set up your first manager below.

Why Luma

  • One workflow across your machines. Place services in cn, global, home, or custom regions. Pin a workload to a named node when placement matters.
  • Separate placement from networking. Run a service at home and expose it through a public relay, or run a cloud worker with no public ingress.
  • Start from an image or a repository. Deploy existing images, build on your machine, or import a GitHub/Gitea repository through a configured Builder.
  • Operate from the console. Inspect applications, logs, routes, builds, registry images, and node health. Manage upgrades with visible progress and route checks.
  • Keep delivery reproducible. Validate manifests, preview a deployment, inspect job history, and roll back a Nomad job version. CI uses the same API as the CLI.
  • Keep configuration scoped. Application secrets and private registry credentials are managed centrally; manifests reference values without embedding them.

Luma fits personal infrastructure and small teams running web apps, APIs, and workers on a few machines. The control plane currently uses one Manager with local SQLite storage. Multi-active Manager high availability and Kubernetes-style tenant isolation are outside that model. See storage and recovery before relying on it for production.

Quick start

1. Install the CLI

On the manager and any machine you want to deploy from:

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

The installer creates an isolated Python environment and places luma in ~/.local/bin. Open a new shell if that directory is not yet on your PATH. Installing the CLI does not bootstrap a server.

Install a pinned version with pip

Python 3.9+ is required. Use a virtual environment on systems with externally managed Python:

python -m venv .venv
. .venv/bin/activate
python -m pip install "luma-infra==0.2.2"

See installation lifecycle for runtime diagnostics and uninstall behavior.

2. Bootstrap one manager

Have these ready:

Requirement Purpose
A Linux server; Ubuntu 22.04+ is the documented starting point Runs Luma Control, Nomad, and Traefik; 2 CPU cores / 2 GB RAM is an evaluation starting point
A domain managed in Cloudflare Control API and application domains
A Cloudflare API token with Zone Read and DNS Edit DNS record management
Public ports 80/443 and an ACME email address HTTP ingress and HTTPS certificates
Access to the configured container registry Pulls the Control image and application images

Run on the manager:

luma bootstrap --domain luma.example.com

The CLI prompts for missing values, provisions the runtime, initializes SQLite, and prints the dashboard URL, management token, and node join token. Keep both tokens private.

If the manager needs a proxy to pull the default GHCR image, configure EGRESS_SUBSCRIPTION_URL before bootstrap. This is especially relevant on mainland China hosts. Tailscale and a Builder Registry are not prerequisites for the first single-manager workload. See getting started for network and host setup.

3. Run the first workload

Open https://luma.example.com/dashboard/, sign in with the management token, and choose Applications → Create application → hello-world first install.

This deploys the repository's hello-world template with exposure: none. It verifies scheduling without adding an application domain. For a public service, use status.yaml above with a domain you own.

From a laptop or CI machine, authenticate and deploy through Control:

luma login https://luma.example.com --token-stdin < token.txt
luma deploy status.yaml
luma status
luma app versions status

luma init scaffolds a manifest like status.yaml, including a memory limit and health check. Image deployment clients need the CLI and access to Control. Local source builds additionally need Docker/Buildx.

How it works

flowchart LR
    Clients[CLI / CI / Dashboard] -->|Authenticated API| Control[Luma Control]
    Control -->|Job specifications| Nomad[Nomad]
    Nomad --> Cloud[Cloud nodes]
    Nomad --> Home[Home nodes]
    Control -->|DNS records| DNS[Cloudflare]
    Visitors[Application traffic] --> Edge[Traefik ingress]
    Edge --> Cloud
    Edge -->|Configured relay| Home

The deployment path and the application traffic path are separate. Control submits jobs; application requests go through the configured ingress. Each workload runs on a Nomad client using the Docker driver.

Placement and exposure

Field Answers Examples
region Where may this service run? cn, global, home, or a custom region
node Must it run on a particular machine? The name registered by luma node join --name
exposure How do clients reach it? cn-edge, external-edge, tailscale-relay, tcp-relay, cloudflare-tunnel, none
proxy Does the container need runtime outbound proxying? true attaches configured egress

For example, region: home with exposure: tailscale-relay places the workload on a home node and sends public traffic through an edge relay over Tailscale. region: cn with exposure: none runs an internal workload without public ingress.

A node pin still respects the region constraint. Runtime proxy: true and image-pull networking are separate concerns. The concepts and exposure model explain these boundaries.

Deployment workflows

Starting point Entry point What happens
Published container image luma deploy app.yaml Deploys the image declared in the manifest
Local source checkout luma build local . --platform linux/amd64 Builds with local Docker/Buildx, uploads to the configured Builder Registry, then deploys
GitHub or Gitea repository luma import <repository-url> Builds on a configured Builder and deploys the result
Multi-service Compose app luma compose deploy luma.compose.yml Deploys an existing Compose file with Luma placement and exposure configuration

luma deploy does not build your source. Choose the build/import workflow when you need a new image. Builder workflows require Builder and Registry setup; plain image deployment does not.

For public services, add a meaningful application health check and explicit memory limits. Rolling behavior depends on ports, volumes, and placement; a successful local validation alone does not prove runtime readiness. Field definitions and examples live in the manifest reference.

Secrets and CI

Keep sensitive values in a local environment file or the secret store:

env:
  DATABASE_URL: ${DATABASE_URL}
luma deploy app.yaml --env .env
# Or store a value interactively under the manifest's application name:
luma secret set DATABASE_URL --scope app

Only referenced variables are imported, under the application's scope. Registry credentials use luma registry login, separately from application environment variables.

CI can use environment variables without creating a persistent login context:

export LUMA_CONTROL_URL="https://luma.example.com"
export LUMA_DEPLOY_TOKEN="$CI_LUMA_MANAGEMENT_TOKEN"

luma validate app.yaml --format json
luma deploy app.yaml --dry-run --format json
luma deploy app.yaml --format ndjson --timeout 3000

LUMA_DEPLOY_TOKEN is the compatibility name for the management token. Treat it as an administrative credential.

Add a node

Run this on the new node, using the node join token printed during bootstrap:

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

Home/private nodes require Tailscale. macOS home nodes also need a running Docker environment such as Docker Desktop or OrbStack. Per-node agent credentials are installed and managed automatically. See getting started and concepts.

Operations at a glance

Task Entry point
Check cluster health luma status and luma doctor
Inspect deployment versions luma app versions <app>
Roll back a Nomad job version luma app rollback <app> --to-version <N>
Upgrade Control and nodes Dashboard → Nodes → Update center
Inspect registry storage Dashboard → Registry
Configure application telemetry and alerts Observability and the independent Observe stack
Back up or recover Manager state Control storage and recovery

Rollback restores a Nomad job version; it does not restore application data. Use immutable images and keep volume/database backups separately. Registry garbage collection is irreversible, and Dashboard Delete and reclaim has no recovery window; the CLI's queued deletion workflow offers a cancellable window before GC. Read the console guide before reclaiming storage.

Documentation

New to Luma? Read Getting started, then Concepts. The documentation index lists every guide.

Guides Reference
Deploying applications Manifest reference
Operations CLI reference
Compose and storage Exposure model
Secrets Control storage and recovery
Console guide Installation lifecycle
Troubleshooting AI agent skills

Development and contributions

git clone https://github.com/LiuTianjie/luma.git
cd luma
python3 -m venv .venv
.venv/bin/python -m pip install -e '.[test]'
npm ci
PATH="$PWD/.venv/bin:$PATH" bash scripts/check-luma.sh

Tests need Python 3.10+ (the runtime still supports 3.9). The gate checks version references, generated CLI docs, ruff, manifest templates, dashboard types/builds, Python and dashboard tests, and whitespace. Tests never read your real ~/.luma.config.json or ./.env. Dashboard source lives in dashboard-src/; its build is packaged into luma/assets/dashboard/.

Focused fixes, reproducible bug reports, and documentation improvements are welcome. For infrastructure bugs, include the Luma version, node role, manifest with secrets removed, expected behavior, and relevant diagnostics. Never post tokens or proxy subscription URLs. See release process and website maintenance for maintainer workflows.

Security and license

Management tokens grant broad access to your cluster. Use the dashboard only on trusted devices; it stores the token in browser local storage. Keep Cloudflare credentials, registry credentials, join tokens, and agent credentials out of repositories and issue reports.

Luma is released under the MIT License. Bundled and external dependencies retain their own licenses.

Metadata

Release files for luma-infra 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for luma-infra 0.2.2
File Size Uploaded
luma_infra-0.2.2.tar.gz 1.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for luma-infra 0.2.2
File Interpreter ABI Platform
luma_infra-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 2.9 MB

Release files / luma_infra-0.2.2.tar.gz

Download URL luma_infra-0.2.2.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
6f026a94c84b32cda8eaa48718f468f621d98abb2612d64f04cb4b2543d3dc07
BLAKE2b-256 checksum
How to use checksums
0268e73e04ae548bb7deba70976fc7f6166a29dc3670d0562cc898ed747cb17a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / luma_infra-0.2.2-py3-none-any.whl

Download URL luma_infra-0.2.2-py3-none-any.whl
Size 1.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
c9bcb63c1c242e112ad147ec2dea6636ab893641a9df794233dcdb3ee14a6683
BLAKE2b-256 checksum
How to use checksums
97d1783cb6ce76511cc33f3f331df9b44add68a74d2df9ef0ff243dcb60f3c94
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.87

2 release files

0.1.85

2 release files

0.1.84

2 release files

0.1.83

2 release files

0.1.82

2 release files

0.1.80

2 release files

0.1.79

2 release files

0.1.78

2 release files

0.1.77

2 release files

0.1.74

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page