Skip to content

Deployment Isolation

HomeStar deploys to a single host. Production is the only environment serving real traffic, and everything that used to be validated on a second machine is now validated in disposable scratch space on the same box.

That was not always the arrangement. Until September 2026 a second stack ran at app.demo.homestar.ink as a staging target and a public playground. It was retired for the reason staging environments usually get retired: it was smaller than the thing it was meant to rehearse, so it failed for its own reasons rather than the code’s, and a check that cries wolf stops being read.

What survives from that design is the isolation machinery, because the canary build needs exactly the same separation a second environment did.

Scopes the Compose project, and with it the volumes, networks, and container names. Every container_name: in docker-compose.yml is written as ${COMPOSE_PROJECT_NAME:-homestar}-<service>, so setting the variable is enough to keep two checkouts on one host from colliding.

/home/deploy/homestar/.env
COMPOSE_PROJECT_NAME=homestar
# the canary build, set inline rather than in a file
COMPOSE_PROJECT_NAME=homestar-canary docker compose --profile prod build

This is what keeps a canary build from overwriting the images prod is running. The canary’s images are tagged homestar-canary-idx-api:latest and friends; prod’s stay homestar-idx-api:latest and are untouched until make promote-prod retags them. Rollback is the same move in reverse: retag the previous image and recreate.

Comma-separated list of Caddy hostnames the frontend claims.

Terminal window
IDX_APP_DOMAINS=app.homestar.ink, ei.homestar.ink, kroetch-property-group.homestar.ink

The compose file supplies a default so the variable is optional:

labels:
caddy: ${IDX_APP_DOMAINS:-app.${DOMAIN}}

Gates the /api/demo/login endpoint that mints ephemeral API keys for the Admin / Owner / Broker / Agent buttons on the login screen. This is a local development convenience and is unrelated to the retired demo host. Leave it set on dev; it is unset on prod, where the endpoint returns 404.

Terminal window
DEMO_MODE=true

The frontend reads PUBLIC_DEMO_MODE to decide whether to render the role buttons. If it is false, only SSO and API-key login appear.

The marketing site’s “See a live site →” CTA reads this, falling back to demoAppUrl in site.json. It points at a real tenant site (kroetch.homestar.ink) rather than a sandbox.

Terminal window
PUBLIC_DEMO_APP_URL=https://kroetch.homestar.ink

Retagging can roll back an image. Nothing rolls back a migration that has already run against the real database, which makes migrations the only part of a deploy that genuinely needs rehearsal.

make canary builds the images in a separate checkout and then runs scripts/migration-gate.sh, which dumps prod’s public schema into a throwaway Postgres container, stamps prod’s current Alembic head, and runs alembic upgrade head against that copy. It deploys nothing.

The gate catches broken DDL, bad down_revision chains, and non-idempotent seeds. It does not catch data-dependent failures: a backfill that trips over one real row will still only fail for real. That limit is worth stating plainly, because a green gate is easy to over-read.

Two details the gate has to mirror from prod or it fails for the wrong reason. The extensions come first, since tables reference vector and postgis types and every CREATE TABLE fails without them. The Alembic files come second, since the image does not ship them; prod mounts them from its checkout, so the gate mounts them from the canary checkout.

Per-brokerage custom domains (a broker who registers www.kroetchpropertygroup.com in the admin) are handled by the idx-domain-advertiser service. It polls the brokerage_domains table every 30 seconds and writes a docker-compose.domains.yml override adding those hostnames to the idx-app Caddy label.

/home/deploy/homestar/ ← prod checkout, the only one serving traffic
/home/deploy/homestar-canary/ ← build + gate scratch space, deploys nothing

Both pull from the same remote. Only prod has a PostgreSQL volume and an Authentik application registration.

A fresh clone on a new machine:

Terminal window
git clone git@git.supported.systems:homestar/homestar.git
cd homestar
cp .env.example .env # then fill in secrets
# 1. Bring up the stack (API, app-dev, postgres, etc.)
make dev
# 2. Seed the primary database with 100 mock properties so the
# frontend has something to render. Defaults to postgres via
# DATABASE_URL — --sqlite opts back into the legacy SQLite path.
make seed
# Visit the dev frontend (HMR enabled):
# https://dev-app.l.homestar.ink
# API:
# https://api.l.homestar.ink/docs

To pull real MLS data against a configured feed, use make sync (single-feed legacy path, defaults to postgres) or make sync-feeds (multi-feed, DB-driven).