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.
Isolation via environment variables
Section titled “Isolation via environment variables”COMPOSE_PROJECT_NAME
Section titled “COMPOSE_PROJECT_NAME”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.
COMPOSE_PROJECT_NAME=homestar
# the canary build, set inline rather than in a fileCOMPOSE_PROJECT_NAME=homestar-canary docker compose --profile prod buildThis 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.
IDX_APP_DOMAINS
Section titled “IDX_APP_DOMAINS”Comma-separated list of Caddy hostnames the frontend claims.
IDX_APP_DOMAINS=app.homestar.ink, ei.homestar.ink, kroetch-property-group.homestar.inkThe compose file supplies a default so the variable is optional:
labels: caddy: ${IDX_APP_DOMAINS:-app.${DOMAIN}}DEMO_MODE
Section titled “DEMO_MODE”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.
DEMO_MODE=trueThe frontend reads PUBLIC_DEMO_MODE to decide whether to render the role
buttons. If it is false, only SSO and API-key login appear.
PUBLIC_DEMO_APP_URL
Section titled “PUBLIC_DEMO_APP_URL”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.
PUBLIC_DEMO_APP_URL=https://kroetch.homestar.inkWhat replaced the second host
Section titled “What replaced the second host”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.
Layering with idx-domain-advertiser
Section titled “Layering with idx-domain-advertiser”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.
Directory layout on the host
Section titled “Directory layout on the host”/home/deploy/homestar/ ← prod checkout, the only one serving traffic/home/deploy/homestar-canary/ ← build + gate scratch space, deploys nothingBoth pull from the same remote. Only prod has a PostgreSQL volume and an Authentik application registration.
Local dev quickstart
Section titled “Local dev quickstart”A fresh clone on a new machine:
git clone git@git.supported.systems:homestar/homestar.gitcd homestarcp .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/docsTo 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).