Skip to content

Setup overview

Setup has two stages: create the core, then create apps on the core. Do not start an app until the core reports live.

flowchart TB
  A["Prepare host,<br/>tailnet and bucket"] --> B["Render core"]
  B --> C["./lk collect"]
  C --> D["./lk run"]
  D --> E{check status}
  E -->|"state: live"| F["Render app<br/>inside repos/"]
  E -->|"state: degraded"| G["Read next:<br/>fix and re-dispatch"]
  G --> E
  F --> H["./lk collect<br/>commit and push"]
  H --> I["Dispatch install<br/>on Forgejo Actions"]
  I --> J{check verify}
  J -->|"verify: green"| K[Operate]
  J -->|"verify: red"| I

Before you begin

One Linux host

Requirement Detail
Access Root SSH over the tailnet address
Docker Present, or installable by install setup
Network Outbound HTTPS; free loopback ports 3000, 8080, 8082, 8083
Tailscale tailscaled running and logged in with its own tagged key
Exclusivity No listener on 0.0.0.0 for a declared port; no second Kamal instance on the host

The host does not need a public IP, an inbound firewall rule, a checkout, Python or Ruby.

One tailnet

  • MagicDNS and HTTPS certificates enabled.
  • An API key from the Tailscale admin console.
  • A tailnet DNS name such as example.ts.net.

One object store

  • An S3-compatible bucket with versioning on.
  • A credential scoped to read, write, delete and list on that bucket.
  • The endpoint, region and bucket name.

Versioning is not optional: retention works by deleting old sets.

A laptop

Tool Version
bash 4 or newer (macOS ships 3.2 — install a current bash)
git, jq, openssl, curl, ssh Current
ruby 3.1 or newer (facts are read with ERB)
python3 3.9 or newer (API clients only)
tailscale 1.92 or newer
Kamal exactly 2.12.0
cookiecutter Current

Run ./lk check preflight --tools to check the laptop, and ./lk check preflight --host to check the host.

Choose your values

Value Notes
instance_slug Two lowercase words. It becomes every container, Service, path and tag name. Pick it once.
tailnet_dns The tailnet's DNS suffix, for example example.ts.net
server_tailnet_ip The host's 100.x tailnet address
backup_bucket, backup_endpoint, backup_region The object store from above
notify_channel, notify_url Where a red check or failed backup is reported
admin_emails, forgejo_admin_user, infisical_admin_email The first accounts

The slug is permanent

Renaming an instance changes its containers, Services, host paths and evidence tags. Treat the slug as part of the deployment, not a label you can edit later.

Stage 1: Core

cookiecutter /path/to/cookiecutter-lairkit-core
cd lairkit-core-<slug>
./lk collect
./lk run
./lk check status

Full details: Set up a core instance.

Stage 2: Apps

cd lairkit-core-<slug>
cookiecutter /path/to/cookiecutter-lairkit-iac-<app> -o repos \
  instance_slug=<two-words> \
  exposure=tailnet-only \
  server_tailnet_ip=100.64.0.10
cd repos/lairkit-iac-<app>-<two-words>
./lk collect
git add -A && git commit -m "initial render" && git push -u origin main

Then dispatch the install workflow on the app repository in Forgejo.

Full details: Set up apps and each app's notes in App operations.

What "done" looks like

A core is done when:

./lk check status    # live
./lk check verify    # green
./lk check backup    # newest set inside the RPO

An app is done when:

./lk check status    # live
./lk check verify    # all probes green
./lk check backup    # newest set inside the RPO

When a step fails

Every action returns one of five codes:

Code Do this
0 Continue
10 Nothing to change; continue
11 Read the failing line and its next: hint
2 Fix the refusal — dirty tree, missing confirmation, missing credential
1 The change ran and failed; read the evidence tag and the failing component

Do not re-run install setup to repair a crash-looping accessory. The rendered runbooks give the repair command, usually ./lk change upgrade --component <name>.