Upgrading a stack9 instance
This guide describes how to upgrade an existing stack9 instance (e.g.
stack9-dxp, eagers-dealership-dxp-stack9, or any other repo
scaffolded from stack9-instance-template) to the latest core version
and dev tooling.
The upgrade is a single command:
yarn stack:up
# or: stack9 stack up
stack9 stack up syncs the canonical files from
stack9-instance-template
into the instance — Vite config, Dockerfiles, root scripts, env
templates — and bumps the @april9au/stack9-* package pins. It is
safe to re-run; instance-owned files (e.g. your docker-compose.yml,
your app.stack9.instance.tsx) are not overwritten.
If you're upgrading an old CRA-based instance for the first time, the CLI also runs a one-shot migration:
- Removes
craco.config.js,react-scripts, and other CRA artifacts - Renames
postcss.config.js/tailwind.config.js→.cjs - Adds
vite,@vitejs/plugin-react,dev/dev:local/build/previewscripts toapps/stack9-frontend/package.json - Removes
lerna.json(NX is now the canonical task runner) - Drops
apps/stack9-frontend/.env.development(replaced by the new.env.local/.envloading thestack9CLI does before each command)
After the command finishes, review the diff, run yarn install, and
boot dev (see below).
Boot dev
yarn dev
That single command:
- Loads
.env.localand.envintoprocess.env(thestack9CLI's env loader) - Brings up postgres + redis + the backend container in Docker
(
docker-compose.dev.yml+docker-compose.dev.backend.yml) - Runs the frontend Vite dev server on the host
- Wires
Ctrl+Cto stop both processes and the docker containers cleanly (scripts/dev.mjs)
Frontend defaults to http://localhost:3333; backend to http://localhost:3336.
Override via STACK9_PORT and STACK9_API_PORT in .env.local.
To run multiple instances side-by-side, also bump STACK9_PG_PORT,
STACK9_REDIS_PORT, STACK9_CONSOLE_PORT, and set
STACK9_PROJECT_NAME to a unique value.
Configuring secrets — two equally-supported paths
Both paths are fully optional and mutually compatible. Pick whichever fits your team.
Path A — manual .env.local
Copy .env.example → .env.local and fill in the values your team
already shares (in 1Password, a team wiki, etc.). The platform reads
every variable from .env.local at boot — no AWS round-trip required.
This is the right default for solo developers, security-restricted environments, or teams that don't use AWS SSM Parameter Store.
Path B — yarn setup (AWS SSM Parameter Store)
For teams already on AWS, the yarn setup workflow pulls shared
secrets from SSM Parameter Store into .env.local for you:
aws sso login --profile <profile>
yarn setup --profile=<profile>
This generates .env.local from /stack9/<project>/local-shared/*
and CDK outputs (per-developer S3 buckets, SQS queues, etc.). Re-run
any time to refresh; existing values in .env.local are preserved
unless you pass --force.
The supporting yarn secrets:* commands (stack9 secrets push/list/rm/migrate)
push, list, prune, and migrate snapshots — see the secrets workflow
reference at the bottom of this page.
Neither
yarn installnoryarn devinvokesyarn setup. The SSM path is strictly opt-in.
APP_* configuration lives in env-vars.json, not .env.local
Important. Application-level config (APP_* keys consumed by your
stack9-stack at runtime — APP_DXP_*, APP_S9_*, etc.) does not
live in .env.local and is not pulled from SSM by yarn setup. It
lives in:
packages/stack9-stack/src/env-vars.json— committed baseline, read by the platform on bootpackages/stack9-stack/src/env-vars.local.json— gitignored per-developer overrides (copyenv-vars.local.json.exampleto start)
Sensitive APP_* values must be marked "sensitive": true and stored
as { iv, encryptedData } in env-vars.json — the platform decrypts
them at runtime.
In local development the legacy APP_SECRETS env-var blob is blocked:
setting it in .env / .env.local would silently bypass
env-vars.json, so the core refuses to start. Deployed environments may
still receive APP_SECRETS in the process environment, for example from
secrets manager via Octopus; Stack9 ignores it in favour of
env-vars.json.
Switching between local and npm-live mode
By default, apps/stack9-frontend consumes @april9au/stack9-{ui, react,sdk} from the dist/ ESM bundles in node_modules — fast
startup, no rebuild loop, but no HMR for those packages.
If you need to test a stack9-monorepo change against a real instance
without publishing a prerelease, use STACK9_LOCAL mode:
yarn dev:local
# ↔ STACK9_LOCAL=true yarn dev
When STACK9_LOCAL=true, the Vite config aliases each
@april9au/stack9-{ui,react,sdk} import to its src/ inside a sibling
stack9-monorepo checkout — edits hot-reload directly without
rebuild or publish.
Sibling-repo layout it expects (relative to apps/stack9-frontend/):
../../../stack9-monorepo/packages/stack9-ui/src/index.tsx
../../../stack9-monorepo/packages/stack9-react/src/index.tsx
../../../stack9-monorepo/packages/stack9-sdk/src/index.ts
Override the path via STACK9_LOCAL_PATH in .env.local if your
checkout differs.
yarn dev (no :local) returns to package mode. The toggle is
per-session — no yarn install needed to switch.
STACK9_LOCAL is an inner-loop convenience, not a release workflow. Publish + pin remains the canonical cross-repo path; see DEVELOPMENT.md in
stack9-monorepo.
Secrets workflow reference
| Command | What it does |
|---|---|
yarn setup | Pull shared parameters from SSM into .env.local. Idempotent. Add --env=<name> to load a teammate snapshot. |
yarn secrets:pull | Refresh shared parameters only (no CDK lookup). |
yarn secrets:push | Push current .env.local shared values back to SSM. Add --env=<name> for a named snapshot, --dry-run to preview, --prune to delete orphans. |
yarn secrets:list | List every snapshot under /stack9/<project>/local*. Add --stale to filter to >90 days. |
yarn secrets:rm | Delete every parameter under a snapshot prefix. |
yarn secrets:migrate | One-time bootstrap: scan existing .env* files → SSM Parameter Store. Run once per team; teammates only need yarn setup after that. |
Common knobs
| Knob | Where | Default | Purpose |
|---|---|---|---|
STACK9_PORT | .env.local | 3333 | Frontend Vite dev server port |
STACK9_API_PORT | .env.local | 3336 | Core API HTTP port |
STACK9_CONSOLE_PORT | .env.local | 3334 | Admin console port (host-side mapping only — internal port is hardcoded to 3334) |
STACK9_PG_PORT | .env.local | 5432 | Postgres host-side port |
STACK9_REDIS_PORT | .env.local | 6379 | Redis host-side port |
STACK9_PROJECT_NAME | .env.local | <repo-name> | Docker Compose project name + SSM prefix /stack9/<project>/.... Bump per-instance. |
STACK9_HOSTNAME | .env.local | localhost | Hostname for SERVER_BASE_URL and auth-cookie domain |
STACK9_LOCAL | shell or env-file | unset | When true, frontend Vite resolves @april9au/stack9-{ui,react,sdk} to local src/ |
STACK9_LOCAL_PATH | .env.local | ../../../stack9-monorepo | Sibling stack9-monorepo path (relative to apps/stack9-frontend/) |
Troubleshooting
yarn devwarns[with-env] Warning: AUTH_SECRETS is not set— populateAUTH_SECRETSin.env.local(see.env.examplefor shape) or runyarn setup --profile=<p>.- Container ports collide between instances — set unique
STACK9_PROJECT_NAMEand bump allSTACK9_*_PORTvalues in.env.local.with-env.mjswill automatically stop conflicting containers from other compose projects on the same ports. - Vite "does not provide an export" at boot — usually a duplicate
copy of
@april9au/stack9-reactis loaded. Check thededupelist inapps/stack9-frontend/vite.config.ts. - STACK9_LOCAL boot crash — usually a CJS dep that Vite's stub
doesn't synthesise a default export for. Add the dep to the local-
src
optimizeDeps.includeblock invite.config.ts.