Local development
Run the Kortix stack on your machine and work with the encrypted API secrets.
For contributors running this repository locally. Requires Docker, Node 22, and pnpm 8.
pnpm install
pnpm dev # full LOCAL stack (web + API + local Supabase + tunnel)Environments
There are four environments for local development, each a separate set of API secrets. They differ only in which backend the API talks to — same code, different DB / Stripe / keys:
| Command | Env | API talks to |
|---|---|---|
pnpm dev | local | 100% local stack — local Supabase/Postgres in Docker, test Stripe. Also runs the web + tunnel. |
pnpm dev:dev-env | dev | the dev stack — dev Supabase DB, test Stripe, dev keys (mirrors dev-api.kortix.com). |
pnpm dev:staging-env | staging | the staging stack — staging Supabase DB and staging keys (mirrors staging-api.kortix.com). |
pnpm dev:prod-env | prod | the prod stack — prod Supabase DB, LIVE Stripe, prod keys (mirrors api.kortix.com). |
pnpm dev is the full local stack you'll use day-to-day. dev:dev-env / dev:staging-env / dev:prod-env run the API locally against the remote dev/staging/prod backend (for debugging DB/billing/account flows) — they don't start local Supabase.
pnpm dev:prod-env connects your local API to production — DB writes, Stripe calls, etc. are real. Use it deliberately.
Verify all profiles decrypt and point at the right stack:
pnpm test:envsHow secrets work
Each environment is a dotenvx-encrypted file, committed to the repo:
| Env | File | Key (in apps/api/.env.keys) |
|---|---|---|
| local | apps/api/.env | DOTENV_PRIVATE_KEY |
| dev | apps/api/.env.dev | DOTENV_PRIVATE_KEY_DEV |
| staging | apps/api/.env.staging | DOTENV_PRIVATE_KEY_STAGING |
| prod | apps/api/.env.prod | DOTENV_PRIVATE_KEY_PROD |
Every value is AES-encrypted (KEY=encrypted:…). The public key that encrypts sits in the file and is safe to commit; the private key that decrypts never touches git — it lives off your machine in Dotenv Armor. At runtime dotenvx run --overload -f <file> decrypts in memory and injects the vars — nothing plaintext is ever written to disk. --overload is intentional for env-specific runs so exported local cloud credentials cannot override the selected profile.
Armor access is split by environment. The six API/web local, dev, and staging
keypairs belong to go-marko-kortix-ai. Only the two .env.prod keypairs
belong to kortix-ai-prod. Always pass the owning team when pushing or
pulling; do not rely on a personal-team default.
These encrypted files are only for local development. The actual deployed production environment does not read them — the prod infra loads its env from AWS Secrets Manager at runtime. So apps/api/.env.prod is just for running locally against the prod backend; changing it does not change what production runs.
apps/web uses the same encrypted profiles (apps/web/.env / .env.dev / .env.staging / .env.prod, own keys in apps/web/.env.keys). Most of it is public (NEXT_PUBLIC_*); only a few Vercel keys are secret. It's decrypted the same way — pnpm dev or pnpm dev:web.
First time on a new machine
Install the key manager and log in
curl -sfS https://dotenvx.sh | sh
curl -sfS https://dotenvx.sh/armor | sh
dotenvx armor loginUse dotenvx 2.7.1 or newer. Existing Armor users upgrading from an older CLI
must run dotenvx armor logout followed by dotenvx armor login once so the
Armor token moves from the settings file into the native OS secret store.
Pull the keys from the cloud
for app in api web; do
(cd "apps/$app" && for f in .env .env.dev .env.staging; do dotenvx armor pull --team go-marko-kortix-ai -f "$f"; done)
doneProduction-authorized users additionally pull the isolated prod keys:
for app in api web; do
(cd "apps/$app" && dotenvx armor pull --team kortix-ai-prod -f .env.prod)
doneEnable the commit hooks + run
git config core.hooksPath .githooks # auto-encrypts secrets on commit
pnpm devThe pre-commit hook auto-encrypts the profile files (.env / .env.dev / .env.staging / .env.prod) so a hand-typed plaintext value is sealed before it can be committed, and blocks the commit if any unencrypted .env slips through.
Everyday
| Task | Command |
|---|---|
| Run (local / dev / staging / prod) | pnpm dev · pnpm dev:dev-env · pnpm dev:staging-env · pnpm dev:prod-env |
| Verify all envs decrypt | pnpm test:envs |
| Read a secret | dotenvx get KEY -f apps/api/.env (or .env.dev / .env.staging / .env.prod) |
| Add / change a secret | dotenvx set KEY value -f apps/api/.env (or .env.dev / .env.staging / .env.prod), then commit |
| Share a new/rotated non-prod key | dotenvx armor push --team go-marko-kortix-ai -f <file> (one file at a time) |
| Share a new/rotated prod key | dotenvx armor push --team kortix-ai-prod -f .env.prod (from the app directory) |
Dotenvx 2.14.0 currently ignores the token supplied to run --token and can
fall back to an interactive Armor login. For automation, first run
dotenvx armor pull --token "$DOTENVX_ARMOR_TOKEN" --team <owning-team> -f <file>
in an ephemeral workspace, then execute with dotenvx run --no-armor -f <file> -- <command>.
Remove the generated .env.keys when the job ends.
The repo-pinned dotenvx 1.75.x still provides rotate; dotenvx 2.x temporarily
removed that command. Use pnpm exec dotenvx rotate, not the global binary.
rotate --no-armor creates a transitional comma-separated old,new private-key
value locally. Do not push that combined value to Armor. Retain only the new key
after decryption verification, push it to the profile's owning team, and prove a
clean pull before merging the rotated ciphertext. Key rotation protects new
ciphertext; rotate the underlying vendor credential too if previously decrypted
secret values must be revoked.
Never write plaintext into the profile files — they're encrypted and committed. Machine-local overrides go in the gitignored apps/api/.env.local, which Bun loads at higher precedence.