Docs

Production deploy

Production is a single Docker host running two containers: the Last Light agent and a Caddy reverse proxy that handles HTTPS automatically. State lives in one Docker volume. The footprint is intentionally small — this is meant to run on a cheap VPS you own, not your production cluster.

Deploy on an isolated host. Last Light runs arbitrary agent-generated code inside per-phase sandboxes (gondolin VMs by default, or Docker containers when LASTLIGHT_SANDBOX=docker). While that isolation is strong, it is early-stage software and may have undiscovered weaknesses. Put it on a host that cannot reach your internal systems, databases, or credentials beyond what it explicitly needs.
Docker-free alternative. On a Linux host with /dev/kvm available, the native systemd deploy runs the harness directly under systemd and uses gondolin for sandboxing — no Docker required. Re-deploys are a git pull plus sudo bash deploy/native/install.sh.

Quick setup (recommended)

The fastest path from a bare server to a running instance — the setup wizard handles environment files, secrets, Docker Compose, and optional Caddy TLS:

npx lastlight setup

It walks you through entering your GitHub App credentials, domain, the repositories the bot manages, your model provider API key (OpenAI / Anthropic / OpenRouter), and optional Slack integration. When done it scaffolds your private deployment overlay at instance/ — writing instance/config.yaml (your managed repos), instance/secrets/.env, and your PEM at instance/secrets/app.pem — then offers to build and start the containers.

Prerequisites: Node.js 20+, Docker, and a GitHub App already created. If you prefer to configure everything manually, follow the steps below instead.

Manual setup

1. Fork and clone

git clone https://github.com/YOUR-USER/lastlight.git
cd lastlight
git remote add upstream https://github.com/cliftonc/lastlight.git

Forking is recommended so you can tweak workflows, prompts, and agent context to match your project. Pull from upstream when you want updates.

2. Lay out your deployment overlay (instance/)

Everything specific to your deployment — managed repos, config overrides, agent-context, and secrets — lives in a single instance/ folder next to docker-compose.yml. It's mounted read-only at /app/instance (LASTLIGHT_OVERLAY_DIR=/app/instance) and is never committed to the public repo or baked into the image — so it's the natural home for a private config repo.

mkdir -p instance/secrets
cp deploy/.env.production.example instance/secrets/.env
cp /path/to/your-app.private-key.pem instance/secrets/app.pem
chmod 600 instance/secrets/.env instance/secrets/app.pem

# instance/config.yaml — at minimum the repos the bot manages:
printf 'managedRepos:\n  - your-org/repo-one\n' > instance/config.yaml

Edit instance/secrets/.env and fill in at minimum:

  • GITHUB_APP_ID, GITHUB_APP_INSTALLATION_ID, GITHUB_APP_PRIVATE_KEY_PATH (use ./app.pem — the entrypoint symlinks it)
  • WEBHOOK_SECRET
  • DOMAIN — your public hostname, used by Caddy for automatic TLS
  • OPENAI_API_KEY, ANTHROPIC_API_KEY, and/or OPENROUTER_API_KEY — must match whichever provider LASTLIGHT_MODEL resolves to (default anthropic/claude-sonnet-4-6). One OpenRouter key covers most providers if you want a single billing surface.

Both the agent and caddy services read instance/secrets/.env via env_file, and the entrypoint sources it inside the container — so no repo-root .env is needed. instance/config.yaml is merged over the public config/default.yaml at startup (maps deep-merge, arrays like managedRepos replace, env vars override). Edit any overlay file and docker compose restart agent to apply — no rebuild.

See the Configuration reference for every variable the harness understands, including optional knobs like LASTLIGHT_MODEL, LASTLIGHT_MODELS, LASTLIGHT_THINKING, LASTLIGHT_SANDBOX, APPROVAL_GATES, ADMIN_PASSWORD, and the Slack OAuth group.

Coming from the OpenCode era? All legacy OPENCODE_* env vars (OPENCODE_MODEL, OPENCODE_MODELS, OPENCODE_VARIANT, OPENCODE_VARIANTS) are still read as fallbacks for the matching LASTLIGHT_* name. You can leave existing .env files in place and rename at your leisure.

3. Build and start

# Point Caddy at your domain (in instance/secrets/.env)
echo "DOMAIN=lastlight.example.com" >> instance/secrets/.env

# Build and start both containers
docker compose build agent
docker compose up -d

# Tail the logs
docker compose logs -f agent

Caddy reads DOMAIN from the environment and provisions a Let's Encrypt certificate on first start. DNS must already resolve to the host.

4. Verify it's running

curl https://lastlight.example.com/health
# { "status": "ok" }

# Invalid webhook signature — returns 401, confirms the listener works
curl -X POST https://lastlight.example.com/webhooks/github -d '{}'

Open https://lastlight.example.com/admin in a browser and check the Home tab. You should see live activity stats, recent workflows, and resource usage.

5. Wire up the webhook

Back in your GitHub App settings:

  • Webhook URLhttps://lastlight.example.com/webhooks/github
  • Webhook secret → same value as WEBHOOK_SECRET in instance/secrets/.env
  • Make sure Active is enabled.

Create a test issue on a repo where the App is installed; Last Light should react within a few seconds and you'll see a new run appear on the dashboard Workflows tab.

State and persistence

All persistent state lives under /app/data inside the container, mounted as a Docker volume:

  • lastlight.db — SQLite: executions, workflow runs, approvals, messaging sessions, rate limits, system status.
  • agent-sessions/projects/ — JSONL session files (Claude-SDK-style envelope written by event-shim.ts — the full audit trail for every run, read by the dashboard). Override the location with LASTLIGHT_SESSIONS_DIR.
  • sandboxes/ — cloned repos, one per task (gondolin or docker).
  • sandbox-data/ — shared volume mounted into docker-mode sandboxes.
  • logs/ — structured harness logs.
  • secrets/app.pem — mode-600 copy of the GitHub App key, used by sandbox containers via the shared volume.

Back this volume up if you care about the audit trail.

Updating

Updating lastlight itself (source, built-in assets) is a rebuild:

git fetch upstream
git merge upstream/main
docker compose build agent
docker compose up -d agent

Updating your overlay (anything in instance/ — managed repos, config, agent-context) is just a restart, no rebuild:

# edit instance/config.yaml (or git pull in instance/), then:
docker compose restart agent

Sessions and the database survive rebuilds because they live in the volume, not the image.