Set up apps¶
An app template renders one instance of an application as its own repository. The repository lives inside the core's repos/ directory, but it has its own git history and remote.
Before you render¶
| Requirement | Detail |
|---|---|
| Core is live | ./lk check status reports live |
| Forgejo admin token | FJ_TOKEN, used once by collect |
| Infisical access | INFISICAL_TOKEN, or INFISICAL_EMAIL and INFISICAL_PASSWORD |
| Public exposure only | A domain, Bunny account and the host's public origin IP |
| App-specific | See the answer table below |
The app host is usually the core host. A dedicated app host is supported by changing server_tailnet_ip; a shared host needs LK_SHARED_HOST=1 when decommissioning.
1. Render inside the core¶
cd lairkit-core-<slug>
cookiecutter /path/to/cookiecutter-lairkit-iac-<app> -o repos \
instance_slug=amber-otter \
exposure=tailnet-only \
server_tailnet_ip=100.64.0.10
The render lands in repos/lairkit-iac-<app>-<slug>/. If you omit instance_slug, the template generates a two-word identity such as antidote-duplicity.
The template walks up from repos/ to find the core checkout, so the core coordinates, backup target, notification channel and owner email come from the core's config/instance.yml instead of being asked again.
For a render outside a core checkout, pass _entrypoint=/path/to/lairkit-core-<slug>.
One directory per instance
Do not render two instances into the same directory, and do not run app commands from the core root. Each render is a separate repository.
Answers that change the shape¶
These prompts are common to every app template:
| Prompt | Values | Effect |
|---|---|---|
instance_slug |
two words | Identity for containers, Services, paths and tags |
exposure |
tailnet-only, public |
Tailnet Services only, or a Bunny pull zone in front of the host |
server_tailnet_ip |
100.x address |
The host that runs the app |
origin_public_ip |
public IP | Required for public; the host's origin address |
base_domain |
domain | Required for public; the Bunny DNS zone |
backup_* |
bucket, endpoint, region | Defaults come from the core |
owner_email, support_email |
addresses | Recorded in the instance facts and reports |
edge_waf_mode, edge_waf_plan |
enforce/off, basic/advanced |
WAF on a public pull zone |
Each app adds its own:
| App | Prompts |
|---|---|
| CryptPad | cryptpad_version, onlyoffice, allow_anonymous_docs, cryptpad_hostname, cryptpad_sandbox_hostname |
| ERPNext | erpnext_version, mariadb_version, redis_version, gunicorn_workers, gunicorn_threads, admin_email, erp_hostname |
| Jitsi Meet | jitsi_release, auth_mode, enable_guests, matrix_admin_username, lldap_base_dn, excalidraw_version, lldap_version, continuwuity_version, sable_version, jitsi_hostname, chat_hostname, matrix_hostname |
| Zipline | zipline_version, postgres_version, pgbackrest_version, storage, s3_*, admin_username, zipline_hostname |
public needs every hostname the app serves. CryptPad needs a main and a sandbox hostname; Jitsi needs main, chat and matrix hostnames. The template refuses a hostname that already resolves, so a new render cannot take over someone else's DNS name. Re-rendering a live instance needs LAIRKIT_SKIP_DNS_CHECK=1.
2. Collect the repository and secrets¶
collect is attended and runs on your laptop. It:
- creates the repository in the core's Forgejo;
- creates the Infisical project and the read-only CI identity;
- writes the Actions secrets and every app secret;
- records the core coordinates in
.lairkit/repo.json; - prints
N/N names resolvewhen every name is readable.
It writes .infisical.json and .lairkit/repo.json in the render. It never writes into the core checkout. A second run is a no-op and exits 10.
Before the first mutation, escrow the credentials the app cannot recreate — the store credentials, the S3 keys and the app's admin passwords — then record the acknowledgement:
./lk check preflight stays red until this value is committed.
3. Push to Forgejo¶
git remote add origin https://<core-slug>-forgejo.<tailnet_dns>/<org>/<project>.git
git push -u origin main
The first push runs check preflight only. It does not install anything.
4. Dispatch the install¶
Mutations run on the core's Forgejo runner, not on your laptop. To dispatch one:
- Open the app repository in Forgejo.
- Open Actions.
- Select install.
- Choose Run workflow.
- Set
actionand run.
Use the same path for the other workflows: change, recover, transfer, retire, and check.
| Step | Action | Result |
|---|---|---|
| 1 | setup |
Boots accessories and the app, then deploys the proxy |
| 2 | admin |
Completes the first-run admin step |
| 3 | ops |
Uploads backup bodies, installs cron, advertises Services, takes the first backup and drill, opens the Operations issue |
ERPNext also has a site action for creating an additional site.
A day-0 install is green when:
./lk check status # live
./lk check verify # all probes green
./lk check backup # newest set inside the RPO
What each install creates¶
| App | Runtime | Data | Backup |
|---|---|---|---|
| CryptPad | CryptPad + OnlyOffice, Dozzle, Kamal proxy | Volume tree (blob, block, customize, data, datastore, OnlyOffice) |
One tar of the volume tree + JSON manifest |
| ERPNext | ERPNext roles + Caddy, MariaDB, two Redis, Dozzle | MariaDB + shared sites/ |
mariadb-dump + sites archive |
| Jitsi Meet | Jitsi web/prosody/jicofo/jvb, Excalidraw, Continuwuity, Sable, lldap, Dozzle, Caddy router | Seven state directories | One tar + JSON manifest |
| Zipline | Zipline, PostgreSQL + pgBackRest, Dozzle | PostgreSQL; uploads on disk or S3 | pgBackRest + uploads archive (local storage) |
Public exposure¶
exposure=public adds a Bunny pull zone and a WAF in front of the app. DNS for the zone is delegated to Bunny. Bunny terminates TLS and forwards HTTP to the origin; the template does not manage the origin firewall, so restrict the origin IP yourself.
Two exceptions matter:
- Jitsi media uses UDP
10000directly toorigin_public_ip. Bunny does not carry the media path. - Dozzle, lldap and other admin surfaces stay on the tailnet in both exposures.