> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> Take an Ooneex application to production — configure the environment, build the bundle, run it, containerize its backing services, and cut a release.

Ooneex runs on [Bun](https://bun.sh). 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

| Stage        | Command                                                                       | What it does                                                                   |
| ------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Configure    | —                                                                             | Set `APP_ENV` and the secrets your app needs (database, cache, storage, JWT).  |
| Build        | [`app:build`](/cli/commands/app-build)                                        | Bundle the `app` module entrypoint into `modules/app/dist`.                    |
| Run          | [`app:start`](/cli/commands/app-start) / [`app:stop`](/cli/commands/app-stop) | Bring up backing services and run every runnable module.                       |
| Containerize | [`docker:create`](/cli/commands/docker-create)                                | Add backing services (Postgres, Redis, …) to `modules/app/docker-compose.yml`. |
| Release      | [`release:create`](/cli/commands/release-create)                              | Bump 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`](/getting-started/configuration) 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`                                                                                                       |

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](/getting-started/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:

```bash theme={null}
# 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](/getting-started/configuration) for how to read these values in code and grant roles per environment.

<Note>
  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.
</Note>

## 2. Build

Build the application from the project root:

```bash theme={null}
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](/cli/commands/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:

```bash theme={null}
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](/cli/commands/app-start).

To stop, `app:stop` runs `docker compose down` in `modules/app`, bringing the backing services back down:

```bash theme={null}
ooneex app:stop
```

Neither command takes options. See [app:stop](/cli/commands/app-stop).

<Note>
  `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.
</Note>

## 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`:

```bash theme={null}
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](/cli/commands/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` (or `mysql`/`mongodb`) for [TypeORM](/components/database); SQLite needs no service, just `SQLITE_DATABASE_PATH`.
* **Cache, queue, rate limit, pub/sub** — `redis` powers [caching](/components/cache) via `CACHE_REDIS_URL` and friends.
* **Storage** — `minio` for S3-compatible local object [storage](/components/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:

```bash theme={null}
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`:

```bash theme={null}
ooneex release:create --module=cli
```

| Option     | Description                                                | Default                  |
| ---------- | ---------------------------------------------------------- | ------------------------ |
| `--module` | Limit the release to the package or module with this name. | All packages and modules |

See [release:create](/cli/commands/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](/advanced/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

<CardGroup cols={2}>
  <Card title="Configuration" icon={<Icon icon="gear" size={16} />} href="/getting-started/configuration">
    The environment-variable and role model that drives the running app.
  </Card>

  <Card title="Testing" icon={<Icon icon="vial" size={16} />} href="/advanced/testing">
    Verify the build before you release it.
  </Card>

  <Card title="Database" icon={<Icon icon="database" size={16} />} href="/components/database">
    TypeORM with PostgreSQL or SQLite, and migrations.
  </Card>

  <Card title="Cache" icon={<Icon icon="bolt" size={16} />} href="/components/cache">
    Redis and filesystem cache backends.
  </Card>

  <Card title="Storage" icon={<Icon icon="box" size={16} />} href="/components/storage">
    Local, Cloudflare R2, and Bunny storage backends.
  </Card>
</CardGroup>
