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.
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, Anthropic API key,
domain, and optional Slack integration. When done it writes .env,
copies your PEM key into place, generates the Docker Compose config, and offers
to build and start the containers.
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 secrets/
Secrets live in a secrets/ directory that's gitignored and mounted
read-only into the container.
mkdir -p secrets
cp .env.example secrets/.env
cp /path/to/your-app.private-key.pem secrets/ Edit secrets/.env and fill in at minimum:
GITHUB_APP_ID,GITHUB_APP_INSTALLATION_ID,GITHUB_APP_PRIVATE_KEY_PATHWEBHOOK_SECRETDOMAIN— your public hostname, used by Caddy for automatic TLSANTHROPIC_API_KEY— optional; skip if using Claude subscription auth
See the Configuration reference for every
variable the harness understands, including optional knobs like
CLAUDE_MODELS, APPROVAL_GATES,
ADMIN_PASSWORD, and the Slack OAuth group.
3. Build and start
# Point Caddy at your domain
echo "DOMAIN=lastlight.example.com" >> 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. First-time Claude auth (subscription mode)
If you're using the Claude subscription (no ANTHROPIC_API_KEY), log
in once inside the container. Always pass --user lastlight
— logging in as root corrupts the credentials file and breaks MCP auth.
docker exec -it --user lastlight lastlight-agent-1 claude login Follow the printed URL in your browser. The token persists in the Docker volume and survives restarts and rebuilds.
5. 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.
6. Wire up the webhook
Back in your GitHub App settings:
- Webhook URL →
https://lastlight.example.com/webhooks/github - Webhook secret → same value as
WEBHOOK_SECRETinsecrets/.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.claude-home/projects/— Agent SDK session JSONL files (the full audit trail for every run).sandboxes/— cloned repos, one per task.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
git fetch upstream
git merge upstream/main
docker compose build agent
docker compose up -d agent Sessions and the database survive rebuilds because they live in the volume, not the image.