Skip to main content

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 / preview scripts to apps/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 / .env loading the stack9 CLI 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:

  1. Loads .env.local and .env into process.env (the stack9 CLI's env loader)
  2. Brings up postgres + redis + the backend container in Docker (docker-compose.dev.yml + docker-compose.dev.backend.yml)
  3. Runs the frontend Vite dev server on the host
  4. Wires Ctrl+C to 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 install nor yarn dev invokes yarn 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 boot
  • packages/stack9-stack/src/env-vars.local.json — gitignored per-developer overrides (copy env-vars.local.json.example to 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

CommandWhat it does
yarn setupPull shared parameters from SSM into .env.local. Idempotent. Add --env=<name> to load a teammate snapshot.
yarn secrets:pullRefresh shared parameters only (no CDK lookup).
yarn secrets:pushPush 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:listList every snapshot under /stack9/<project>/local*. Add --stale to filter to >90 days.
yarn secrets:rmDelete every parameter under a snapshot prefix.
yarn secrets:migrateOne-time bootstrap: scan existing .env* files → SSM Parameter Store. Run once per team; teammates only need yarn setup after that.

Common knobs

KnobWhereDefaultPurpose
STACK9_PORT.env.local3333Frontend Vite dev server port
STACK9_API_PORT.env.local3336Core API HTTP port
STACK9_CONSOLE_PORT.env.local3334Admin console port (host-side mapping only — internal port is hardcoded to 3334)
STACK9_PG_PORT.env.local5432Postgres host-side port
STACK9_REDIS_PORT.env.local6379Redis host-side port
STACK9_PROJECT_NAME.env.local<repo-name>Docker Compose project name + SSM prefix /stack9/<project>/.... Bump per-instance.
STACK9_HOSTNAME.env.locallocalhostHostname for SERVER_BASE_URL and auth-cookie domain
STACK9_LOCALshell or env-fileunsetWhen true, frontend Vite resolves @april9au/stack9-{ui,react,sdk} to local src/
STACK9_LOCAL_PATH.env.local../../../stack9-monorepoSibling stack9-monorepo path (relative to apps/stack9-frontend/)

Troubleshooting

  • yarn dev warns [with-env] Warning: AUTH_SECRETS is not set — populate AUTH_SECRETS in .env.local (see .env.example for shape) or run yarn setup --profile=<p>.
  • Container ports collide between instances — set unique STACK9_PROJECT_NAME and bump all STACK9_*_PORT values in .env.local. with-env.mjs will 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-react is loaded. Check the dedupe list in apps/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.include block in vite.config.ts.