# Deploy an app

kixctl deploys from git repositories you register. A registered repository holds everything kixctl needs to fetch and build an app: its clone URL, branch, build attribute, and an optional webhook secret. Two triggers land a deploy — a webhook or a poll — and both produce the identical build.

The repository must be a Nix flake describing the app as a `kixctl.app` spec. That spec, and a full first-deploy walkthrough, are in [Your first deploy](../getting-started/first-deploy.md).

## Add a repository

Manage repositories under **Settings → Repositories**.

1. **Settings → Repositories → Add repository.**
2. **Repository** — `owner/repo` as the host reports it (for example `kixago/demo-app`). This is the name a webhook payload identifies itself by.
3. **Clone URL** — the SSH URL (recommended) or a public HTTPS URL.
4. Optional: **Name** (the stable slug for the app's address and its per-revision instances; blank derives it from the repository name), **Branch** (blank tracks the default branch), **Build attribute** (blank uses the install default), **Webhook secret**, and the **Poll** toggle and interval.

The minimum is a name and a clone URL. A push or the next poll deploys it.

## SSH first

The clone URL carries the authentication. An SSH URL — `ssh://git@host:port/owner/repo.git` — authenticates through the access the host already has, so a private repository needs no key or token stored in kixctl. A public HTTPS URL works with no credentials. The same clone URL is used for both the poll (`git ls-remote`) and the pinned build (`git+<clone_url>?rev=<sha>`).

A repository on a non-standard SSH port must include the port in the URL (`ssh://git@git.lan.example.com:2222/…`).

## Webhook or poll

- **Webhook** is the low-latency path. Set a webhook secret on the repository, then point the host's webhook at the endpoint for its dialect: Forgejo, Gitea, and Codeberg use `POST /api/deploy/forgejo` (or `/gitea`, `/codeberg`); GitHub uses `POST /api/deploy/github`. The repository is resolved by the payload's `full_name` and verified against its own secret, so one endpoint serves every repository.
- **Poll** is the host-agnostic baseline. Leave the secret blank and kixctl checks the repository on its interval with `git ls-remote`, deploying a new commit with no webhook — the right choice for a mirror or a host you can't webhook.

Both triggers deploy only the tracked branch. A push to any other branch is acknowledged and ignored.

## Deploy now

**Deploy now** on a repository row checks it immediately and deploys its latest commit if that commit isn't already running. It runs the same path on the queue, so progress appears on the **Updates** tab like any other deploy. Use it to force a retry after a failed build — a normal poll holds a commit it already tried, so it won't rebuild a broken commit every minute.

## What a deploy does

A deploy builds a NixOS image pinned to the exact commit, imports it over the Incus API, and launches it as an immutable instance named `<slug>-<sha7>` on kixctl's own network. The first revision of an app is published so it is reachable by name; a later revision lands alongside the running one and surfaces as **update ready** for you to promote. See [Update and cut over](./update-and-cutover.md).

## What kixctl stores

Stored, encrypted at rest under `APP_KEY`: the optional per-repository webhook secret. Not stored: any SSH key or access token — SSH authenticates through the host's existing access. Removing a repository deletes its registration only; running revisions and routes belong to the cluster and are left in place.
