Deployment
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.
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.
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 installwith access to two secret files:web_envmounted to/app/apps/admin/.env.localserver_envmounted to/app/apps/server/.env.local
- Sets
NODE_ENV=production. - Changes to the
apps/serverdirectory and runsbun run buildto compile the server source code.
Runner Stage
- Also uses the digest-pinned
oven/bun:1.4.4base image. - Sets the working directory to
/app. - Copies the built output from the builder stage (
/appfrom 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.
Secrets
Two Docker secrets are defined in the secrets section of docker-compose.yml:
web_env: sourced fromapps/admin/.envserver_env: sourced fromapps/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:
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:
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=osPOSTGRES_USER=postgresPOSTGRES_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 affectingopenwiki/**orapps/docs/**. - Steps:
- Checks out the repository.
- Sets up Bun.
- Installs dependencies with
bun install --frozen-lockfile. - Builds the documentation site with
bun run --filter docs build. - Uploads the built site (located in
apps/docs/dist) as an artifact nameddocs-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
mainaffectingapps/**,packages/**,docs/adr/**,docs/architecture/**, orpackage.json. - Steps:
- Checks out the repository with
fetch-depth: 0(to ensure full history for the OpenWiki tool). - Sets up Node.js (version 22).
- Installs the OpenWiki CLI and dependencies globally (
openwiki@0.7.2,mermaid@11.16.0,jsdom@29.1.1). - Runs
openwiki code --update --printto update the wiki based on code changes. - Opens a pull request (via
peter-evans/create-pull-request) containing the wiki updates under theopenwikidirectory.
- Checks out the repository with
- 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:
- Build and push the new Docker image to a registry (if not building locally).
- Pull the new image on the host:
docker compose pull server. - 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. - Health checks (defined in the
serverservice) 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:
- Orchestration Platform: Deploy to Kubernetes or Docker Swarm, which support rolling update strategies (e.g.,
maxSurgeandmaxUnavailablein Kubernetes Deployments). - Load Balancing: Use a reverse proxy or load balancer (e.g., Nginx, Traefik) to distribute traffic across multiple server instances.
- Health Checks: Ensure the application’s health endpoint (as defined in the
docker-compose.ymlhealthcheck) is reliable and prevents traffic to unhealthy instances. - 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.