# How Backend Services Are Managed in Openship: Architecture and Lifecycle

> Discover how Openship manages backend services by treating deployable units as database-backed entities. Learn about automated routing, drift detection, and deployment tracking for Docker Compose and monorepos.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: architecture
- Published: 2026-07-29

---

**Openship treats every deployable unit as a database-backed service entity, supporting both Docker Compose and monorepo architectures while automating routing, drift detection, and deployment state tracking.**

Openship provides a comprehensive system for managing backend services within deployment projects. The platform abstracts every deployable unit—whether a Docker Compose entry or a monorepo sub-application—as a **service** backed by a centralized PostgreSQL schema. This architecture enables consistent handling of routing configuration, environment variables, and deployment lifecycles across diverse project types.

## Service Types and Database Schema

Openship recognizes two distinct service kinds defined in the database schema at [`packages/db/src/schema/service.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/service.ts):

- **`kind = "compose"`** – Represents traditional Docker Compose services with fields for `image`, `build` context, and port mappings.
- **`kind = "monorepo"`** – Represents sub-applications within a monorepo, storing `rootDirectory`, install commands, build commands, start commands, and framework metadata.

The `service` table serves as the single source of truth, capturing both infrastructure-agnostic metadata and runtime-specific configuration. Public routing is handled through scalar columns (`exposed`, `exposedPort`, `domain`, `customDomain`, `domainType`) alongside a JSONB `publicEndpoints` array. This structure allows a single service exposing multiple ports to maintain distinct routing rules for each endpoint.

## CRUD Operations and Business Logic

The core service management logic resides in [`apps/api/src/modules/services/service.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/services/service.service.ts). This module exposes several key operations:

- **`createService` and `updateService`** – Validate incoming data through `normalizeRoutingPatch` to ensure routing consistency, enforce custom hostname rules via `isValidCustomHostname`, and persist changes through the `repos.service` data-access layer.
- **`listServices` and `getService`** – Retrieve service rows while enriching the response with any pending **drift** detected from upstream compose file changes.

All routing mutations trigger domain record updates through helper functions that interface with the DNS and certificate management systems.

## Drift Management for Compose Services

When a project's [`docker-compose.yml`](https://github.com/oblien/openship/blob/main/docker-compose.yml) file changes, Openship calculates a **drift** between the stored baseline (`importedSpec`) and the newly parsed specification. The system stores this diff in the `driftSpec` column, presenting users with two resolution paths:

1. **`acceptServiceDrift`** – Merges the pending changes into the service row, updating fields such as image references, port mappings, and environment variables to match the compose file.
2. **`keepServiceDrift`** – Discards the detected drift, maintaining the current baseline configuration despite changes in the repository.

This mechanism ensures that manual overrides made through the Openship interface are not silently overwritten by repository updates, while still providing a clear path to synchronize with upstream changes.

## Routing and Domain Configuration

Routing orchestration is delegated to [`apps/api/src/lib/routing-domains.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/lib/routing-domains.ts). This library synthesizes OpenResty edge configurations through two primary functions:

- **`buildServiceRouteDomain`** – Generates the vhost configuration for a single service endpoint.
- **`buildServiceRouteDomains`** – Processes the `publicEndpoints` array to create multiple route definitions for services with several exposed ports.

After service creation or routing field modifications, the service layer invokes `ensurePendingServiceDomain` to provision DNS records and TLS certificates, or `removeServiceDomain` to tear them down. These operations execute with a `ROUTE_EDGE_APPLY_TIMEOUT_MS` of 6000 milliseconds to prevent slow remote edge updates from blocking API responses.

## Deployment Pipeline and State Tracking

Per-service deployment status is maintained in the `service_deployment` table, defined alongside the main service schema. The deployment flow follows this sequence:

1. The **deployment controller** at [`apps/api/src/modules/deployments/deployment.controller.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/deployments/deployment.controller.ts) initiates a build session.
2. The **build service** ([`apps/api/src/modules/deployments/build.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/deployments/build.service.ts)) iterates over enabled services, creating a `ServiceHandle` via `buildServiceHandle` (referenced in [`backup.orchestrator.ts`](https://github.com/oblien/openship/blob/main/backup.orchestrator.ts) and [`restore.orchestrator.ts`](https://github.com/oblien/openship/blob/main/restore.orchestrator.ts)).
3. The system selects the appropriate runtime—Docker for compose services or bare execution for monorepo apps—based on the service's `kind` field.
4. Upon completion, the `service_deployment` row updates with final status values (`success`, `failed`, or `skipped`) alongside timestamps (`startedAt`, `finishedAt`).

## Service Lifecycle and Cleanup

Services can be temporarily excluded from deployments without database deletion by setting `enabled = false`. Permanent removal triggers the **project cleanup service** at [`apps/api/src/modules/projects/project-cleanup.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/projects/project-cleanup.service.ts), which invokes `buildServiceRouteDomain` to purge routing configurations and associated resources.

The system also enforces plan limitations through `assertFreeEndpointsAllowed`, preventing accidental exposure of free sub-domains when the organization's subscription does not permit them.

## Summary

- Openship unifies Docker Compose and monorepo workflows under a single **service** abstraction stored in PostgreSQL.
- The [`service.service.ts`](https://github.com/oblien/openship/blob/main/service.service.ts) module handles CRUD operations, routing validation, and drift management for repository-driven configuration changes.
- **Drift detection** allows users to accept or reject changes originating from modified [`docker-compose.yml`](https://github.com/oblien/openship/blob/main/docker-compose.yml) files.
- Routing configuration is generated via [`routing-domains.ts`](https://github.com/oblien/openship/blob/main/routing-domains.ts) and applied with a 6-second timeout to edge proxies.
- Deployment state is tracked per-service in the `service_deployment` table, with builds orchestrated through the deployment controller and build service.

## Frequently Asked Questions

### What is the difference between compose and monorepo services in Openship?

Compose services map directly to Docker Compose entries, utilizing fields like `image`, `build` context, and Docker-specific port syntax. Monorepo services represent sub-applications within a larger repository, storing framework metadata and command scripts (`installCommand`, `buildCommand`, `startCommand`) alongside a `rootDirectory` path. Both types share the same routing and deployment infrastructure but execute through different runtimes during the build phase.

### How does Openship handle changes to docker-compose.yml files?

When Openship detects modifications to a project's [`docker-compose.yml`](https://github.com/oblien/openship/blob/main/docker-compose.yml), it parses the new specification and compares it against the stored `importedSpec` baseline. The resulting difference is stored as a **drift** in the `driftSpec` column. Users must explicitly choose to `acceptServiceDrift` (applying the changes to the service configuration) or `keepServiceDrift` (preserving the current settings), preventing automatic overwrites of manual configuration adjustments.

### What happens when a service is deployed in Openship?

The deployment controller triggers the build service, which creates a `ServiceHandle` for each enabled service and selects the appropriate runtime based on the `kind` field. The system executes the build process, streams logs to the client, and updates the `service_deployment` table with the final status and timestamps. If the service is exposed publicly, the routing library simultaneously provisions DNS records and TLS certificates through the domain management helpers.

### How does Openship manage custom domains and SSL certificates?

Custom domains are validated through `isValidCustomHostname` and stored in the `customDomain` column. The `ensurePendingServiceDomain` function provisions the necessary DNS records and TLS certificates, while `buildServiceRouteDomain` generates the corresponding OpenResty configuration. When services are deleted or routing is disabled, `removeServiceDomain` tears down these resources to prevent orphaned certificates and DNS entries.