Fork the repo, drop in your secrets, docker compose up. Caddy handles TLS automatically.
For the authoritative step-by-step — including every optional env var, Slack OAuth login, approval gates, and the dashboard password — read Production deploy in the docs.
Last Light is a powerful autonomous agent. While it runs all code in isolated Docker sandbox containers, it is early-stage software that may have undetected security issues. Deploy it on an isolated host — a cheap VPS, a separate cloud project, or a dedicated VM — where it cannot reach sensitive internal systems, databases, or credentials beyond what it explicitly needs.
Fork Last Light so you can customize skills, tweak prompts, and track your own changes. Pull upstream updates whenever you want.
# Fork on GitHub first, then clone your fork
git clone https://github.com/YOUR-USERNAME/lastlight.git
cd lastlight
# Add upstream so you can pull updates later
git remote add upstream https://github.com/cliftonc/lastlight.git Your skills, deployment config, and Dockerfile are yours to modify. The skills/ directory is where all the behavior lives — edit freely.
All secrets live in a secrets/ directory that's gitignored and mounted read-only into the container.
# Copy the environment template
cp .env.example secrets/.env
# Place your GitHub App private key
cp ~/path-to/your-app.private-key.pem secrets/ Edit secrets/.env with your:
localhost for dev)claude login for subscription-based auth)CLAUDE_MODELS as JSON to route architect/executor/reviewer phases to different modelsAPPROVAL_GATES=post_architect,post_reviewer to pause at human-in-the-loop checkpointsADMIN_PASSWORD and a stable ADMIN_SECRET for persistent login sessionsSLACK_BOT_TOKEN and SLACK_APP_TOKEN for the in-process chat skill (Socket Mode)SLACK_OAUTH_CLIENT_ID, SLACK_OAUTH_CLIENT_SECRET, SLACK_OAUTH_REDIRECT_URI, and optionally SLACK_ALLOWED_WORKSPACE to restrict login to one teamSee the Configuration reference for every variable the harness reads, with defaults and descriptions.
The Docker image is self-contained — Claude Code CLI, the MCP server, all skills, and dependencies are baked in. Secrets are injected at runtime via the mounted volume.
# Set your public domain (for Caddy TLS)
export DOMAIN=lastlight.example.com
# Build and start
docker compose up -d
# Watch the logs
docker compose logs -f agent If this is your first time, log in to Claude Code inside the container:
# One-time auth — persists in the Docker volume
docker exec -it lastlight-agent-1 claude login Follow the URL in your browser to authenticate. The auth token survives container restarts and rebuilds.
What happens:
DOMAIN=localhost — Caddy serves HTTP only, no TLSVerify it's running:
# Health check
curl https://lastlight.example.com/health
# Or locally
curl http://localhost/health If you already run Caddy, Nginx, or another reverse proxy on the host, the bundled Caddy container will fail with "address already in use" on ports 80/443. Instead, disable it and point your existing proxy at the agent:
1. Create a docker-compose.override.yml to disable the Caddy service and expose the agent port locally:
# docker-compose.override.yml
services:
agent:
ports:
- "127.0.0.1:8644:8644"
caddy:
profiles:
- disabled 2. Add a reverse proxy entry in your existing config. For Caddy:
lastlight.example.com {
reverse_proxy localhost:8644
} For Nginx:
server {
listen 443 ssl;
server_name lastlight.example.com;
location / {
proxy_pass http://127.0.0.1:8644;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
} 3. Start only the agent: docker compose up -d agent
In your GitHub App settings, set the webhook URL and subscribe to events.
https://lastlight.example.com/webhooks/githubWEBHOOK_SECRET in your secrets/.envInstall the GitHub App on the repos (or org) you want Last Light to manage. Test by opening an issue — the bot should triage it within seconds.
The same Docker image works in K8s. Skip Caddy — use your cluster's Ingress controller for TLS instead.
apiVersion: apps/v1
kind: Deployment
metadata:
name: lastlight
spec:
replicas: 1
selector:
matchLabels:
app: lastlight
template:
metadata:
labels:
app: lastlight
spec:
containers:
- name: agent
image: your-registry/lastlight:latest
ports:
- containerPort: 8644
volumeMounts:
- name: secrets
mountPath: /opt/lastlight/secrets
readOnly: true
- name: state
mountPath: /opt/lastlight/sessions
volumes:
- name: secrets
secret:
secretName: lastlight-secrets
- name: state
persistentVolumeClaim:
claimName: lastlight-state
---
apiVersion: v1
kind: Service
metadata:
name: lastlight
spec:
selector:
app: lastlight
ports:
- port: 8644
targetPort: 8644 Create the K8s Secret from your local files: kubectl create secret generic lastlight-secrets --from-file=.env=secrets/.env --from-file=your-app.pem=secrets/your-app.private-key.pem
Pull upstream changes, rebuild the image, restart:
git fetch upstream
git merge upstream/main
docker compose build
docker compose up -d This also updates Claude Code CLI (installed during the build). To pick up the latest CLI without any Last Light changes, just rebuild:
docker compose build --no-cache agent
docker compose up -d agent GitHub App tokens expire hourly. The MCP server refreshes them automatically — no action needed. If you see auth errors, restart the agent container.
GitHub App installations get 5,000 API calls/hour. For most repos that's plenty. If you hit limits, increase cron intervals or reduce repos per installation.
Each triage or review is ~1 agent session. Build requests use more tokens. Locally, your Claude Code subscription covers the cost. For production, costs depend on your ANTHROPIC_API_KEY usage.
Session logs are saved as JSON trajectories. View them with docker compose logs agent or inspect the agent-logs volume.
One instance handles all repos where the GitHub App is installed. No per-repo config needed — skills apply universally.