---
type: concept
title: Deployment
description: Documentation of the deployment process for the OpenServer application, covering Docker image building, environment configuration, CI/CD pipelines for documentation and wiki updates, and considerations for rolling updates in a multi-tenant setup.
tags: [deployment, docker, ci/cd, environment]
verified:
  - by: openwiki/0.7.2
    at: 2026-10-14T20:42:32.326Z
---

# Deployment

This document outlines the deployment process for the OpenServer application, including how Docker images are built, how environment variables and secrets are managed, the existing CI/CD pipelines for documentation and wiki updates, and considerations for rolling updates in a multi-tenant context.

## Docker Image Building

The application uses a multi-stage Docker build to produce a minimal production image. The build process is defined in [`apps/server/Dockerfile`](/operations/apps/server/Dockerfile).

### Builder Stage
- Uses `oven/bun:1.4.4`, pinned by digest, as the base image.
- Sets the working directory to `/app`.
- Copies the entire repository into the image.
- Installs dependencies using `bun install` with access to two secret files:
  - `web_env` mounted to `/app/apps/admin/.env.local`
  - `server_env` mounted to `/app/apps/server/.env.local`
- Sets `NODE_ENV=production`.
- Changes to the `apps/server` directory and runs `bun run build` to compile the server source code.

### Runner Stage
- Also uses the digest-pinned `oven/bun:1.4.4` base image.
- Sets the working directory to `/app`.
- Copies the built output from the builder stage (`/app` from the builder) into the runner image.
- Sets `NODE_ENV=production`.
- Exposes port `3000`.
- Sets the working directory to `/app/apps/server`.
- Defines the container's default command as `["bun", "dist/index.mjs"]` to run the built server.

This approach ensures that the final image contains only the compiled output and runtime dependencies, minimizing the attack surface and image size.

## Environment Configuration

Environment-specific configuration is managed through a combination of Docker secrets, environment files, and direct environment variable assignments in [`docker-compose.yml`](./docker-compose.yml).

### Secrets
Two Docker secrets are defined in the `secrets` section of `docker-compose.yml`:
- `web_env`: sourced from `apps/admin/.env`
- `server_env`: sourced from `apps/server/.env`

These secrets are made available to the `server` service during the build process (via `RUN --mount=type=secret` in the Dockerfile) and at runtime (though the Dockerfile only uses them during build; the runner stage does not explicitly mount them, relying instead on the built artifact and other environment variables).

### Environment File
The `server` service specifies an optional environment file:
```yaml
env_file:
  - path: apps/server/.env
    required: false
```
If `apps/server/.env` exists, its variables are loaded into the container's environment.

### Direct Environment Variables
The `server` service sets the `DATABASE_URL` environment variable directly:
```yaml
environment:
  DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-password}@postgres:5432/os
```
This variable references the `POSTGRES_PASSWORD` environment variable (which can be set in the host environment or via a `.env` file read by Docker Compose), providing a default of `password` if unset.

### Postgres Service
The `postgres` service defines its own environment variables for database initialization:
- `POSTGRES_DB=os`
- `POSTGRES_USER=postgres`
- `POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}`

These variables are used by the official PostgreSQL image to create the database and user on container startup.

### Summary
Sensitive values (such as API keys or database passwords) should be stored in the secret files (`apps/admin/.env` and `apps/server/.env`) or managed via the host environment (for `POSTGRES_PASSWORD`). Non-sensitive configuration can be placed in `apps/server/.env`. The Docker build process has access to the secrets, while the runtime container primarily relies on the built artifact and the environment variables set in `docker-compose.yml` (including those from the env_file and direct assignments).

## CI/CD Pipeline

The repository includes two GitHub Actions workflows that automate documentation and wiki updates, but **do not** automatically build and deploy the application Docker image to a runtime environment.

### Documentation Build (`.github/workflows/docs.yml`)
- **Triggers**: Workflow dispatch, push to `main`, or pull request affecting `openwiki/**` or `apps/docs/**`.
- **Steps**:
  1. Checks out the repository.
  2. Sets up Bun.
  3. Installs dependencies with `bun install --frozen-lockfile`.
  4. Builds the documentation site with `bun run --filter docs build`.
  5. Uploads the built site (located in `apps/docs/dist`) as an artifact named `docs-site`.
- **Note**: The workflow currently only uploads the artifact; deployment of the docs site is manual or handled externally.

### OpenWiki Update (`.github/workflows/openwiki-update.yml`)
- **Triggers**: Workflow dispatch or push to `main` affecting `apps/**`, `packages/**`, `docs/adr/**`, `docs/architecture/**`, or `package.json`.
- **Steps**:
  1. Checks out the repository with `fetch-depth: 0` (to ensure full history for the OpenWiki tool).
  2. Sets up Node.js (version 22).
  3. Installs the OpenWiki CLI and dependencies globally (`openwiki@0.7.2`, `mermaid@11.16.0`, `jsdom@29.1.1`).
  4. Runs `openwiki code --update --print` to update the wiki based on code changes.
  5. Opens a pull request (via `peter-evans/create-pull-request`) containing the wiki updates under the `openwiki` directory.
- **Note**: This workflow automates the generation and proposing of wiki updates but does not merge them automatically; human review is required.

### Application Deployment
There is currently no configured CI/CD pipeline for building and deploying the application Docker image to a staging or production environment. Deployment of the server container is typically done manually using `docker compose up -d` or integrated into an external deployment system (e.g., a custom script or CI system that builds the image and pushes it to a registry, then triggers a rolling update on a cluster).

## Rolling Updates and Multi-Tenant Safety

The current deployment setup uses plain Docker Compose (`docker-compose.yml`) without an orchestration layer that natively supports rolling updates (such as Docker Swarm or Kubernetes). Therefore, achieving zero-downtime rolling updates requires additional considerations.

### Statelessness and Data Separation
- The application server is designed to be stateless; all persistent data is stored in the PostgreSQL database.
- Secrets and configuration are externalized (via secrets, env files, and environment variables), allowing the same image to be run across different environments.
- This separation allows for replacing the application container without data loss, provided the database schema remains compatible.

### Manual Rolling Update Procedure
To perform a rolling update with minimal downtime using the current setup:
1. **Build and push** the new Docker image to a registry (if not building locally).
2. **Pull the new image** on the host: `docker compose pull server`.
3. **Stop the old container** and start the new one with `docker compose up -d --no-deps --build server`. Docker Compose will recreate the container with the new image.
4. **Health checks** (defined in the `server` service) can be used to verify the new container is ready before considering the update complete.

However, this procedure results in a brief downtime while the container is recreated. For true zero-downtime updates, a load balancer and multiple instances of the server are required, allowing traffic to be shifted gradually from old to new instances.

### Multi-Tenant Considerations
In a multi-tenant deployment where multiple tenants share the same application instance:
- **Database migrations** must be backward-compatible to allow rolling updates without breaking older versions of the application temporarily running alongside new ones.
- **Feature flags** or versioned APIs can help manage transitions.
- **Shared resources** (like the database) must be monitored for contention during updates.

### Recommendations for Zero-Downtime Deployments
To achieve rolling updates with no downtime, consider:
1. **Orchestration Platform**: Deploy to Kubernetes or Docker Swarm, which support rolling update strategies (e.g., `maxSurge` and `maxUnavailable` in Kubernetes Deployments).
2. **Load Balancing**: Use a reverse proxy or load balancer (e.g., Nginx, Traefik) to distribute traffic across multiple server instances.
3. **Health Checks**: Ensure the application's health endpoint (as defined in the `docker-compose.yml` healthcheck) is reliable and prevents traffic to unhealthy instances.
4. **Database Migration Strategy**: Use tools that support online schema changes and deploy migrations in a backward-compatible manner.

Until such an orchestrator is in place, the current Docker Compose setup is suitable for development, staging, or single-instance production deployments where brief downtime during updates is acceptable.

## Related Documentation
- For initial setup instructions, see [Setup](/setup).
- For logging configuration and practices, see [Logging](/logging).