Skip to content

Set up a core instance

This page takes you from an empty directory to a live core: Forgejo, Infisical, a runner, logs, Pages and backups.

1. Render the repository

cookiecutter /path/to/cookiecutter-lairkit-core

Answer the prompts:

Prompt Meaning Example
instance_slug Permanent identity for every derived name quiet-heron
tailnet_dns Tailnet DNS suffix example.ts.net
server_tailnet_ip Host's tailnet address 100.64.0.10
admin_emails Comma-separated admin emails ops@example.com
forgejo_admin_user First Forgejo account operator
forgejo_admin_email First Forgejo email ops@example.com
infisical_admin_email First Infisical email ops@example.com
backup_bucket Object-store bucket lairkit-core
backup_endpoint S3 endpoint https://s3.example.com
backup_region S3 region us-east-1
notify_channel Alert channel webhook
notify_url Alert destination https://…

The render lands in lairkit-core-<slug>/. Commit it before running anything that mutates.

cd lairkit-core-<slug>
git add -A && git commit -m "initial render"

2. Finish the console steps

Three things have no API and are done once, by a named person.

Step Where What to keep
Tailscale API key Tailscale admin → Settings → Keys → Generate access token TS_API_KEY
Object store Provider console → bucket → Access keys; enable versioning S3_ACCESS_KEY, S3_SECRET_KEY
DNS Tailscale admin → DNS → enable MagicDNS and HTTPS certificates Nothing; confirm certificates can issue

The Forgejo admin password is generated for you. Do not create it in a browser.

./lk check preflight --tailnet and ./lk check preflight --store prove the first two steps. The store check uploads a scratch object, reads it back byte-for-byte and deletes it.

3. Collect facts and secrets

./lk collect

collect is the only prompting command. It:

  • fills any remaining placeholder in config/instance.yml;
  • stores TS_API_KEY and the S3 pair in .kamal/bootstrap.env (mode 0600, gitignored);
  • generates the backend passwords and tokens;
  • commits the tree and, if there is no remote, creates a local bare remote at .git/lk-origin.git.

The escrow record is printed once by install setup (or by ./lk run), not by collect.

To script it, export the three values and skip the prompts:

export TS_API_KEY=... S3_ACCESS_KEY=... S3_SECRET_KEY=...
./lk collect --non-interactive

Escrow before the first mutation

The escrow record contains values that cannot be recreated, including the Forgejo and Infisical encryption material. Store it offline before running ./lk run. install setup refuses to start without the acknowledgement.

4. Run the install

./lk run

run performs the bring-up in the only order that works on a fresh tailnet:

Stage What happens
preflight Tools, facts, host and object store are checked before anything mutates
net Tailnet policy is applied, the host is tagged, the CI tag is created, four Services are advertised
setup Forgejo config is rendered, the escrow record is printed once, then one kamal setup --skip-push boots the ten accessories and waits for readiness
admin Forgejo admin, org, teams, runner, Infisical project and identities, demo site
ops Host backup/restore bodies, cron, first backup, first drill, Operations issue
verify Four service checks

Each stage is a separate dispatch with its own lock, evidence tag and exit code. A stage that changes nothing returns 10 and the run continues. A red stage stops the run and prints the stage name and exit code.

run is idempotent. A second run changes nothing and exits 10.

Run the stages yourself

./lk check preflight --tools --facts --host
./lk install net --reason "first install"
./lk install setup --escrow-ack --reason "first install"
./lk install admin --reason "first install"
./lk install ops --reason "first install"
./lk check verify

5. Check the result

./lk check status

A healthy core reports live and prints the earliest credential expiry and one next: line. Then:

./lk check verify
./lk check backup

You should have four tailnet URLs:

Service URL
Forgejo https://<slug>-forgejo.<tailnet_dns>
Infisical https://<slug>-infisical.<tailnet_dns>
Dozzle https://<slug>-dozzle.<tailnet_dns>
Pages https://<slug>-pages.<tailnet_dns>

The demo site is published at:

https://<slug>-pages.<tailnet_dns>/demo-site/

Sign in to Forgejo as forgejo_admin_user. The password is FORGEJO_ADMIN_PASSWORD in .kamal/bootstrap.env; copy it to your password manager.

The bootstrap file stays

.kamal/bootstrap.env is retained at mode 0600 because the database passwords, encryption keys, pgBackRest cipher and S3 pair must be readable while Infisical is down during a resume, reboot or restore. It is gitignored. Infisical remains the remote source of truth.

6. What is now running

Part Count
Tailnet node 1 (the host)
Kamal accessories 10
HTTP Services 4
Facts file 1 (config/instance.yml)
Secret store project 1 (lairkit-core-<slug>)
Durable client state 0

Troubleshooting

Symptom Code Next
Dirty tree, wrong checkout or foreign lock 2 ./lk check status names the lock holder; clean or push the tree
Preflight red 11 Read the failing line's remedy; nothing was mutated
Accessory crash-looping after setup 1 ./lk check incident, then ./lk change upgrade --component <name> --reason "repair a failed boot"
Tailnet policy preview differs 2 Edit policy/tailnet.yml; nothing was applied
First backup failed 1 The tag names the failing body; ./lk check backup prints the last error
Facts still show placeholders — Run ./lk collect again; check the line it names

Do not re-run install setup to restart a stopped accessory. Setup does not restart an existing container; the repair path is change upgrade.

Next