Memory
Architecture decision record 0002 accepted

Run platform services as composed containers on a single host

Docker Compose on one Linux host rather than a container orchestrator, for as long as the workload genuinely fits.

  • Runtime
  • Infrastructure
  • Operations

2 min read · 512 words

Context01

The forces at the time

Nexus consists of a small number of long-lived services — a reverse proxy, a database, a message broker, and a handful of module APIs. They are operated by one person. There is no availability requirement beyond "it should come back on its own after a power cut", and no load that requires more than one machine.

The pull toward an orchestrator is real and mostly professional rather than technical: it is what production looks like elsewhere. The question is whether that is a reason here.

Decision02

What was chosen

Run every service as a container on a single Linux host, declared in Docker Compose files — one stack per module, plus a stack for shared platform services. Restart policies handle recovery after reboot.

Two network classes are mandatory. An edge network is shared with the reverse proxy and carries only what is meant to be reachable. A data network is marked internal and has no route off the host. Databases attach only to data . No service publishes a host port.

yamlThe shape every module stack follows
services:
  api:
    build: ./api
    depends_on: [db, broker]
    networks: [edge, data]
    # No published ports. The proxy is the only way in.

  db:
    image: postgres:16-alpine
    volumes: [pgdata:/var/lib/postgresql/data]
    networks: [data]

networks:
  edge: { external: true }   # shared with the reverse proxy
  data: { internal: true }   # no route off the host

This decision is explicitly scoped by its premise. It holds while the workload fits one host and is operated by one person. If either changes, this record should be superseded rather than quietly stretched.

Alternatives03

What else was considered

Kubernetes
Rejected for this scale. A control plane is itself a system to operate, upgrade, and debug, and its value is in scheduling across nodes — capability that does not apply to one machine.
Docker Swarm
Rejected. Lighter than Kubernetes and closer to the Compose file format, but it still introduces cluster state to manage in exchange for multi-node scheduling that is not needed.
Systemd units on the host
Rejected. Fewer moving parts and excellent supervision, but dependency isolation becomes the host’s problem and rebuilding the machine stops being reliable.
A managed cloud platform
Rejected. The point of this platform is that it runs on hardware under direct control, on the same network as the devices it serves. That requirement is the project, not an implementation choice.
Consequences04

What follows

Gained

  • The full runtime definition is a handful of readable files in version control.
  • A rebuild is a checkout and a compose up, which makes reproducibility testable rather than theoretical.
  • No control plane to operate, upgrade, or debug at 1am.
  • Internal networks give the database no route off the host regardless of service behaviour.

Paid for

  • The host is a single point of failure. Maintenance is downtime.
  • No automatic rescheduling. A failed container restarts in place or not at all.
  • Scaling beyond one machine means replacing this decision, not extending it.
  • Compose does not cover secret management, which needs a separate answer.

Records are immutable once accepted. A change supersedes this one rather than editing it.