Skip to main content
Ooneex runs on Bun. There is no separate “deploy” package: a production deployment is the sum of a few real CLI commands and the environment-variable model that drives the app at boot. This guide walks the path from a configured project to a running production service, then to a tagged release.

The path to production

StageCommandWhat it does
ConfigureSet APP_ENV and the secrets your app needs (database, cache, storage, JWT).
Buildapp:buildBundle the app module entrypoint into modules/app/dist.
Runapp:start / app:stopBring up backing services and run every runnable module.
Containerizedocker:createAdd backing services (Postgres, Redis, …) to modules/app/docker-compose.yml.
Releaserelease:createBump versions, write changelogs, tag, and push.
Each stage builds on the one before it. The sections below cover them in order.

1. Environment and configuration

Environment selection is driven entirely by the APP_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:
GroupValues
Local & devlocal, development
Pre-prodstaging, testing, test, qa, uat, integration, preview, demo, sandbox, beta, canary, hotfix
Productionproduction
Each value exposes a matching boolean on the injected AppEnv instance (isProduction, isStaging, isLocal, …) so code can branch on the active environment.

Production environment variables

In source the environment is authored in modules/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:
# App
APP_ENV=production
PORT=3000                 # defaults to 3000
HOST_NAME=0.0.0.0         # defaults to 0.0.0.0

# Database — see /components/database
DATABASE_URL=postgresql://user:password@db:5432/app
SQLITE_DATABASE_PATH=/data/app.sqlite   # when using SQLite instead

# Cache — see /components/cache
CACHE_REDIS_URL=redis://cache:6379

# Storage — see /components/storage
STORAGE_CLOUDFLARE_ACCESS_KEY=...
STORAGE_CLOUDFLARE_SECRET_KEY=...
STORAGE_CLOUDFLARE_ENDPOINT=...
STORAGE_CLOUDFLARE_REGION=auto
STORAGE_BUNNY_ACCESS_KEY=...
STORAGE_BUNNY_STORAGE_ZONE=...
FILESYSTEM_STORAGE_PATH=/data/uploads   # local filesystem storage

# Secrets
JWT_SECRET=change-me-in-production
Only set the variables for the backends you actually use — empty values stay unset, so an unconfigured service simply remains inactive. The full typed surface (logs, analytics, rate limiting, queue, mailer, AI, payment, search, per-environment allowed users) lives on the 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:
ooneex app:build
This reads the 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:
ooneex app:start
It reads the 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:
ooneex app:stop
Neither command takes options. See app:stop.
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 at modules/app/docker-compose.yml. Add a predefined service with docker:create:
ooneex docker:create --name=postgres
ooneex docker:create --name=redis
The command merges the chosen service block (and any volumes) into the existing file, creating it if it does not exist, and ensures the app’s 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.
OptionDescriptionDefault
--nameThe 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:
  • Databasepostgres (or mysql/mongodb) for TypeORM; SQLite needs no service, just SQLITE_DATABASE_PATH.
  • Cache, queue, rate limit, pub/subredis powers caching via CACHE_REDIS_URL and friends.
  • Storageminio for S3-compatible local object storage, or point STORAGE_* at Cloudflare R2 / Bunny in production.
In production, run your built app container next to these services and connect over the Compose network using service names as hostnames (for example 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:
ooneex release:create
For every package and module with unreleased commits since its last tag, it determines the version bump (breaking change → major, 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:
ooneex release:create --module=cli
OptionDescriptionDefault
--moduleLimit the release to the package or module with this name.All packages and modules
See release:create.

Deployment checklist

Before promoting a build to production:
  • Set APP_ENV=production explicitly 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/PORT for 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:build targets the Bun runtime.
  • Cut a release with release:create so the deployed commit is tagged and traceable.

Best practices

  • Treat configuration as environment. Keep .env.yml for 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 only APP_ENV and the injected secrets.
  • Containerize stateful dependencies. Use docker:create to 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:create so 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.