auberge deploy
Deploy one or more apps — or a whole Host composition — to a host. Hardening runs first automatically, then infrastructure dependencies are resolved. Alias: auberge dp.
auberge deploy [apps...] [OPTIONS]Options
Section titled “Options”| Option | Description | Default |
|---|---|---|
apps... |
App or playbook names (positional, variadic) | Interactive |
-H, --host HOST |
Target host | Interactive |
--all |
Deploy every apps.yml app |
false |
-C, --check |
Dry-run (ansible check mode) | false |
-f, --force |
Skip confirmation prompt | false |
--verify-public-dns |
Verify each app’s public A record after deploy | false |
Examples
Section titled “Examples”auberge deploy # interactiveauberge deploy paperless freshrss --host prodauberge deploy --all --host prod -f # CI/CDauberge deploy paperless --check # previewauberge deploy paperless --host prod --verify-public-dnsauberge deploy ruche --host ruche # a whole compositionExecution order
Section titled “Execution order”Every deploy runs hardening → infrastructure → apps in that order. Hardening (firewall, fail2ban, kernel) is mandatory and untagged. To skip it, use auberge ansible run directly.
The CLI shows the resolved plan before running (suppress with -f):
Execution plan: Host: prod (203.0.113.10) → hardening → infrastructure → apps (tags: paperless, freshrss)Composition targets
Section titled “Composition targets”A name outside the apps.yml roster resolves to the standalone playbook of that name and runs after Substrate:
Execution plan: Host: ruche (203.0.113.42) → hardening → infrastructure → rucheruche is the agent tier’s composition — machine-user identity, the OpenCode runtime and the aoe dashboard. A composition run pulls infrastructure.yml in for the same reason an app does: the dashboard is unreachable until Caddy holds a certificate for its zone (ADR-0075).
[!IMPORTANT] Every entry on
ruche.yml— and onaoe.ymlandopencode.yml— is guarded onwhen: "'agent' in group_names", so the Host must carrytags = ["agent"]inhosts.tomlor the run is a green no-op that installs nothing. Check it withauberge host list; the TAGS column must showagent. This is thehosts.tomltagsfield (an ansible group, deciding which roles run) — nottailnet_tag, which happens to take the same word for the ACL tier and decides what the Host may reach on the tailnet. They are unrelated fields (ADR-0062).
bootstrap is deliberately not a deploy target, because it is a lifecycle operation rather than a convergence:
| Playbook | Run it with | Why not deploy |
|---|---|---|
bootstrap |
auberge ansible bootstrap |
connects as root on port 22 before the ansible user exists |
A name that is both an apps.yml role and a standalone playbook (calibre, immich, gokapi, hermes) keeps going through the roster, which is where those apps have always deployed from.
Interactive selection
Section titled “Interactive selection”auberge deploy with no positional args offers every target — the apps.yml roster in declaration order, then the standalone playbooks the roster does not already hold, marked as such:
Select app(s) or playbook(s) to deploy (space to toggle, enter to confirm)> [all apps] paperless freshrss … aoe (playbook) memsearch (playbook) opencode (playbook) ruche (playbook)The marker reads (playbook) and not (composition) because only ruche is a Composition — a Playbook whose roster is several Apps. aoe, opencode and memsearch are plain standalone Playbooks, and one word has to cover all four.
[all apps] selects the roster and nothing else — the same set --all deploys. A playbook is opt-in by name, because a deploy builds its Preflight for the whole plan before the first task runs: sweeping ruche into [all apps] would make every host in the fleet demand aoe_passphrase, opencode_openrouter_api_key and agents_domain (ADR-0077). Picking [all apps] alongside individual entries deploys the union.
Substrate Apps
Section titled “Substrate Apps”Substrate Apps (Caddy, Blocky, Headscale) are declared in infrastructure.yml, not apps.yml, so they are not valid deploy targets — they deploy on every auberge deploy <app> run instead. To push one alone:
auberge ansible run -t blockyDNS verification
Section titled “DNS verification”After each app’s playbook run (not in --check). A run’s apps are its tags where it has them, and its roster where it does not — so a composition is checked through the apps on it:
- Tailnet-only apps (apps declaring
{app}_tailscale_ip): the tailnet’s Blocky is queried on UDP/53 and must answer with the app’s own address. Blocky is the host whose config answersblocky_subdomain, at thetailscale_ipthatauberge host detect-tailscale-ipcached for it — a different host from the app’s as soon as the fleet grows. - Public apps: opt-in via
--verify-public-dns.1.1.1.1is queried; the A record must matchansible_host. Opt-in because Cloudflare propagation can lag.
A mismatch aborts the deploy:
DNS mismatch for essaim.example.com: queried 100.64.0.1, expected 100.64.0.9, got [100.64.0.1]So does not knowing which host answers for the tailnet — a tailnet-only app’s records live in that Blocky or nowhere, so an unanswerable check is a failed one:
Cannot verify essaim.example.com on the tailnet: 2 Hosts answer `blocky_subdomain` (auberge, ruche), but the tailnet has one resolver (ADR-0052); withdraw the gate on the others with `[hosts.<name>] blocky_subdomain = ""`