The path to production
| Stage | Command | What it does |
|---|---|---|
| Configure | — | Set APP_ENV and the secrets your app needs (database, cache, storage, JWT). |
| Build | app:build | Bundle the app module entrypoint into modules/app/dist. |
| Run | app:start / app:stop | Bring up backing services and run every runnable module. |
| Containerize | docker:create | Add backing services (Postgres, Redis, …) to modules/app/docker-compose.yml. |
| Release | release:create | Bump versions, write changelogs, tag, and push. |
1. Environment and configuration
Environment selection is driven entirely by theAPP_ENV variable, read by @ooneex/app-env at boot. When APP_ENV is unset or blank it defaults to production — so a production host that forgets to set it still behaves as production rather than silently falling back to development.
APP_ENV accepts one value from the Environment enum:
| Group | Values |
|---|---|
| Local & dev | local, development |
| Pre-prod | staging, testing, test, qa, uat, integration, preview, demo, sandbox, beta, canary, hotfix |
| Production | production |
AppEnv instance (isProduction, isStaging, isLocal, …) so code can branch on the active environment.
Production environment variables
In source the environment is authored inmodules/shared/.env.yml, which is flattened into uppercase variables at boot (see Configuration). In production you typically inject the same variables directly through your platform’s secret manager or container environment rather than committing the YAML. The variables AppEnv reads include:
AppEnv class; see Configuration for how to read these values in code and grant roles per environment.
Never commit real production secrets. Inject
APP_ENV and credentials through
your CI/CD pipeline or container environment, and keep .env.yml for local
development only.2. Build
Build the application from the project root:app module’s package.json for the application name and runs bun build ./src/index.ts --outdir ./dist --target bun inside modules/app, producing the bundled output in modules/app/dist. The command takes no options — see app:build.
The --target bun output is meant to run on the Bun runtime, so your production image or host needs Bun installed (or you run the bundle with the same Bun version you built with).
3. Run
app:start brings up the application from the project root:
app module’s package.json, brings up the Docker services defined in modules/app/docker-compose.yml when that file is present, discovers every spa, microservice, and api module under modules/, and runs them concurrently. api and microservice modules serve their entrypoint with hot reload; spa modules run their dev server. See app:start.
To stop, app:stop runs docker compose down in modules/app, bringing the backing services back down:
app:start runs modules in hot-reload / dev-server mode and is ideal for a
staging or local run against real services. For a hardened production process,
run the built bundle directly under your process manager with APP_ENV=production
set, alongside the containerized services from the next section.4. Containerize the backing services
Ooneex manages your backing services through a single Compose file atmodules/app/docker-compose.yml. Add a predefined service with docker:create:
package.json has a dev script. Run it without --name to be prompted. The available services are:
clickhouse, elasticsearch, grafana, jaeger, keycloak, libretranslate, maildev, memcached, minio, mongodb, mysql, nats, postgres, prometheus, rabbitmq, redis, temporal, vault.
See docker:create for the full reference.
| Option | Description | Default |
|---|---|---|
--name | The docker service to add (one of the values above). | Prompted if omitted |
Deploying the app alongside its services
The supported persistence and infrastructure backends map directly onto these services:- Database —
postgres(ormysql/mongodb) for TypeORM; SQLite needs no service, justSQLITE_DATABASE_PATH. - Cache, queue, rate limit, pub/sub —
redispowers caching viaCACHE_REDIS_URLand friends. - Storage —
miniofor S3-compatible local object storage, or pointSTORAGE_*at Cloudflare R2 / Bunny in production.
DATABASE_URL=postgresql://user:pass@postgres:5432/app, CACHE_REDIS_URL=redis://redis:6379). Mount volumes for any stateful service (Postgres data, MinIO buckets, SQLite/filesystem paths) so data survives container restarts.
5. Release
When the code is ready to ship,release:create cuts versioned releases from your conventional commits:
feat → minor, otherwise patch), updates package.json and CHANGELOG.md, commits the changes, and creates an annotated git tag. After releasing it asks whether to push commits and tags; confirming runs bun install, commits bun.lock, and pushes to the remote.
Scope a release to a single target with --module:
| Option | Description | Default |
|---|---|---|
--module | Limit the release to the package or module with this name. | All packages and modules |
Deployment checklist
Before promoting a build to production:- Set
APP_ENV=productionexplicitly on the host, even though it defaults to production — make the intent visible. - Provide every required secret through the environment (
DATABASE_URL, cache URLs,STORAGE_*,JWT_SECRET, …). Empty values disable their feature, so a missing secret fails silently — verify what the app actually needs. - Run database migrations before the new build serves traffic, so the schema matches the deployed code.
- Configure persistent storage and a real cache backend — point storage at Cloudflare R2 / Bunny (or a volume-backed MinIO) and use Redis rather than the filesystem cache fallback under load.
- Wire health and logs — set
HOST_NAME/PORTfor your platform’s probes and configure log/exception sinks (LOGS_DATABASE_URL, BetterStack tokens) so production output is captured. - Pin the Bun version used to build and run, since
app:buildtargets the Bun runtime. - Cut a release with
release:createso the deployed commit is tagged and traceable.
Best practices
- Treat configuration as environment. Keep
.env.ymlfor local development and inject production values per environment; never bake secrets into the image. - Separate build from run. Build once with
app:build, then promote the same artifact across environments by changing onlyAPP_ENVand the injected secrets. - Containerize stateful dependencies. Use
docker:createto declare your services in one Compose file and give every stateful one a persistent volume. - Verify before you ship. Run your test suite (see Testing) against the build, then release.
- Tag every deploy. Use
release:createso each production rollout corresponds to an annotated git tag and changelog entry.
Next steps
Configuration
The environment-variable and role model that drives the running app.
Testing
Verify the build before you release it.
Database
TypeORM with PostgreSQL or SQLite, and migrations.
Cache
Redis and filesystem cache backends.
Storage
Local, Cloudflare R2, and Bunny storage backends.