# Deployment — redeploy to `cochi` Production host for the Ulicraft stack. | | | |---|---| | Host | `cochi` (SSH alias) | | Path | `/home/ubuntu/mc/ulicraft-server-v1` | | Branch | `main` | | Compose | single `docker-compose.yml` (no overrides) | | Auth | Drasl (password login) | | Ingress | host nginx (Let's Encrypt TLS) → caddy → services (see 15-letsencrypt.md) | | Restart | **hard** (`compose down` → `compose up --build`) — brief full downtime | ## Routine redeploy ```bash ssh cochi cd /home/ubuntu/mc/ulicraft-server-v1 git pull --ff-only tooling/render-config.sh # re-render drasl/nmsr configs from .env tooling/sync-server-mods.sh # sync ./server-mods from $DISTRIBUTION_WEB_ROOT docker compose down --remove-orphans docker compose up -d --build ``` Or as a one-shot from your workstation: ```bash ssh cochi 'set -e cd /home/ubuntu/mc/ulicraft-server-v1 git pull --ff-only tooling/render-config.sh tooling/sync-server-mods.sh docker compose down --remove-orphans docker compose up -d --build' ``` `render-config.sh` re-renders the drasl/nmsr configs from `.env`; `sync-server-mods.sh` mirrors the `both`/`server` mod subset (from `mods-sides.json`) out of `$DISTRIBUTION_WEB_ROOT` into `./server-mods` before the stack comes back up. `$DISTRIBUTION_WEB_ROOT` must resolve on the host (it also backs the caddy `distribution.` mount). ### Landing rebuild (not part of `compose`) The landing site (incl. `/register`) is static `www/`, gitignored — a `git pull` does **not** update it. Rebuild on the host whenever `landing/` changed or you toggled `REGISTRATION_MODE` (it bakes the invite-field on/off at build, and Drasl's `RequireInvite` is derived from the same key by `render-config.sh`): ```bash ( cd landing && set -a && . ../.env && set +a && pnpm run build ) # → www/ docker compose restart drasl # apply CORS / RateLimit / RequireInvite ``` `down`/`up` is not needed for a landing-only or flag-only change — rebuild `www/` and restart `drasl`. ## Known host-local divergence on `cochi` (uncommitted) `cochi` carries deliberate local edits that are **not in the repo**, so `git pull --ff-only` **aborts** ("local changes would be overwritten") whenever an incoming commit also touches `docker-compose.yml`. As of 2026-06-09 the host has, uncommitted: - `docker-compose.yml` — internal URLs forced `http`→`https` (`JVM_OPTS` authlib-injector), for the in-container HTTPS path. (The old `PACKWIZ_URL` divergence is moot — packwiz was removed; see `04-mods.md`.) - `dnsmasq/` (new), `caddy/caddy-root-ca.crt`, `landing/pnpm-workspace.yaml` — internal DNS + private CA so containers resolve/trust `https://auth.` etc. - `landing/package.json` modified. (The former `pack/` divergence is resolved — `pack/` and `custom/` were deleted with the packwiz removal.) **Do not steamroll it.** Before a deploy that edits `docker-compose.yml`, either: 1. **Converge** — commit the host edits to `main` + push, then a clean ff-pull deploys (preferred — ends the divergence). The https/dnsmasq/CA setup belongs in the repo. 2. **Stash–pull–pop** — `git stash` → `git pull --ff-only` → `git stash pop`. Non-destructive; safe only when the incoming and host edits are in different regions of the file. Stop on a pop conflict. Never `git reset --hard` / discard on the host without confirming first. ## What survives a hard restart Named volumes persist — world and auth data are safe: - `mc_data` — world + installed NeoForge/mods - `drasl_state` — Drasl accounts/skins - `kuma_data` — Uptime Kuma config/history - `backups/` — mc-backup snapshots `down` removes **containers**, not volumes. Players are disconnected during the restart (a few minutes); they reconnect once `minecraft` is healthy again. ## Prerequisites (one-time, on `cochi`) - SSH access as the `cochi` alias. - `.env` present and complete: `BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT`, `CADDY_HTTP_BIND`, `DISTRIBUTION_WEB_ROOT`, `REGISTRATION_MODE` (`invite`|`open`, defaults `invite`), and the Let's Encrypt block (`render-config.sh` halts on an unset `BASE_DOMAIN` or an invalid `REGISTRATION_MODE`). - Docker + compose plugin, `envsubst`, `jq` (used by `sync-server-mods.sh`; falls back to `python3` if absent), `git`. - Host nginx + Let's Encrypt certs installed once — see `15-letsencrypt.md`. - **First deploy only** — online prep (build landing, fetch launcher, fetch authlib into `runtime/`). Not needed for routine redeploys. ## Verify ```bash docker compose ps # caddy, drasl, minecraft, mc-backup, nmsr, mc-status, uptime-kuma up # self-hosted live status feed (plan/15-mc-status.md): curl -fsS "https://$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)/api/mcstatus/v2/status/java/$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)" | jq .online docker compose logs -f minecraft # watch for "Done" curl -fsS "https://auth.$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)/" >/dev/null && echo "auth ok" ``` ## Rollback ```bash git log --oneline -5 # find the last-good commit git checkout # or: git reset --hard docker compose down --remove-orphans docker compose up -d --build ``` ## Notes - Production needs internet on `cochi` at restart: the server installs/re-syncs NeoForge + mods over the network. - All ingress goes through host nginx → caddy. After a redeploy, nginx config is unchanged; only re-run `tooling/render-nginx.sh --install` if `BASE_DOMAIN` / `CADDY_HTTP_PORT` changed (see `15-letsencrypt.md`).