Prompt: deploy a containerised web app on an ird server at your hostname
Have an agent put a Docker Compose app on an ird VPS behind Caddy — TLS
issued automatically, the hostname's A record set on your ird zone,
secrets copied out of band — with a repeatable redeploy that keeps the
proxy up, a snapshot before every risky upgrade, and the logs to prove
it works.
Needs: ird access (MCP server or the ird CLI, logged in); an existing ird server with Docker and a non-root deploy user (from the vps-server prompt); a shell with ssh/scp; the app's compose file or image; a hostname on a zone hosted on ird DNS
Costs: the server's hourly tier as before (credits_prices → recurring.vps-hourly); each snapshot per GB-hour while kept (recurring.image-hourly)
Group: servers
Paste everything below the line into a fresh conversation. Fill in the
FILL IN block first.
Task
Deploy the app below on SERVER so it answers at https://HOST, and
leave me a redeploy procedure. No server is created or destroyed here;
snapshots are taken only after I confirm.
FILL IN
SERVER: app-1 # name or id from vps_list — "none" = go to the vps-server prompt first
HOST: app.example.ro # hostname on a zone hosted on ird DNS
ZONE: example.ro
APP_DIR: ./ # holds compose.yaml (and Dockerfile if building)
IMAGE: ghcr.io/example/app:1.4.2 # image:tag, or "build" to build from APP_DIR on the server
APP_PORT: 8080 # port the app container listens on
ENV_FILE: ./.env.production # secrets; copied to the server, never pasted anywhere
DEPLOY_USER: deploy # non-root user in the docker group
HEALTH: /healthz # path returning 200 when the app is ready
Non-negotiable rules
- No server, no provisioning here.
vps_list must show SERVER
running. If it doesn't exist, stop and point me to the
vps-server prompt — that's where price and creation are confirmed.
- Snapshot before anything destructive. Image upgrades and schema
migrations get
ird vps snapshot SERVER pre-<tag> first (bills per
GB-hour, CLI-only); ask before taking it, delete it once the release
is verified.
- Secrets stay out of band. ENV_FILE travels by
scp, lands with
mode 600, is referenced via env_file:, and is never quoted in chat,
committed, or baked into the image.
- Whole record sets.
dns_set_record replaces the entire RRset of
that name+type; read dns_get_zone first and touch only HOST.
Never call vps_destroy.
- Only Caddy is public. The app container publishes no host ports;
Caddy takes 80/443. Check
ufw status allows exactly those (plus 22).
Steps
A. Server. vps_list (CLI: ird vps show SERVER) → id, ipv4,
state. ssh DEPLOY_USER@<ipv4> docker compose version must work; if
Docker is missing, the vps-server prompt's hardening step installs it.
B. DNS. dns_list_zones includes ZONE. dns_get_zone(ZONE); then
dns_set_record(domain=ZONE, name="HOST.", type="A",
data={address: <ipv4>}) (CLI: ird dns set ZONE <short name> A
<ipv4> — the CLI takes app, not the full HOST). Add an AAAA only if
Caddy will actually answer on the server's ipv6 — a stale AAAA breaks
certificate issuance. dig +short HOST = ipv4 before step D; Caddy
can't get a certificate until the name resolves.
C. Files on the server in /srv/HOST/:
compose.yaml: service caddy (caddy:2, ports 80:80 and
443:443, volumes caddy_data, caddy_config, the Caddyfile
mounted read-only, restart: unless-stopped) and service app
(image: IMAGE or build: ., env_file: .env, expose: [APP_PORT],
a healthcheck hitting HEALTH, restart: unless-stopped).
Caddyfile: HOST { reverse_proxy app:APP_PORT } — that one line is
the TLS setup.
.env: scp ENV_FILE DEPLOY_USER@<ipv4>:/srv/HOST/.env && ssh … chmod 600.
D. First deploy. docker compose pull (or build),
docker compose up -d, docker compose ps (both healthy),
docker compose logs caddy shows the certificate obtained, then
curl -sS https://HOST/HEALTH returns 200 and curl -vI https://HOST
shows a valid chain.
E. Redeploy. For each release: (1) if it migrates data, rule 2 —
ird vps snapshot SERVER pre-<tag>, wait until
ird vps snapshot images lists it available (ird vps activity: the
create-image job succeeded); (2) change the tag in
compose.yaml; (3) docker compose pull app && docker compose up -d
--no-deps app. Caddy stays up throughout; the only gap is the seconds
until the new app container passes its healthcheck. Rollback = put the
previous tag back and repeat (3); a broken migration means restoring the
snapshot (the vps-snapshot-restore prompt).
F. Verify. curl -sS https://HOST/HEALTH → 200;
docker compose ps healthy; docker compose logs --tail=100 app clean;
docker image prune -f for old layers; vps_list still running.
G. Cleanup. Once the release has run for the agreed time:
ird vps snapshot images delete <image-id> for the pre-upgrade
snapshot — it bills per GB-hour until removed. Confirm with
ird vps snapshot images.
Deliverable
https://HOST, the server id and IP, the A record set, the compose and
Caddyfile as deployed (secrets redacted), the health and certificate
checks, the three-command redeploy, the rollback line, and the snapshot
id still held (if any) with its monthly cost.