Skip to content

How dockmesh thinks about Stacks

To use dockmesh well, it helps to understand how it thinks about stacks. The design is opinionated; the model is small.

Every stack in dockmesh corresponds to one docker compose project:

  • One compose.yaml file
  • Zero or more services (containers)
  • Zero or more volumes, networks, configs
  • A unique project name (the stack’s name, which Compose stamps onto every container as com.docker.compose.project)

This is exactly what docker compose calls a project. dockmesh doesn’t add a new abstraction — it uses the Compose spec.

Your compose.yaml lives on disk at <stacks-root>/<stack>/compose.yaml. One directory per stack, no host subdirectory — the host a stack is currently deployed on is tracked separately in the stack_deployments DB row, not encoded in the path. This keeps the on-disk tree stable when you migrate a stack between hosts.

The dockmesh database stores:

  • Deployment metadata (which host, last deploy status, deploy history)
  • Audit log entries
  • Per-stack scaling rules, environment overlays, dependency declarations (in .dockmesh.meta.json next to compose, plus mirror tables for queryability)
  • Template + Git-source bindings, if applicable

But not the compose content itself. If you delete the dockmesh DB, your stacks still exist on disk. If you edit a file on disk (manually, via Git pull, via your editor), dockmesh’s fsnotify watcher detects the change — the next deploy applies it. There is a 2-second self-write suppression window so dockmesh-initiated writes don’t get re-flagged as “external” changes.

This is deliberate. It means:

  • You can mix CLI (docker compose up) and dockmesh UI
  • Git-backed stacks (pull from a repo) work naturally
  • Disaster recovery is “restore the stacks/ directory and the DB”
  • No vendor lock-in — uninstall dockmesh and your stacks keep working with plain docker compose

A stack’s compose.yaml is host-neutral on disk, but every deployment lands on exactly one host. The binding is recorded in stack_deployments(stack_name, host_id, status, deployed_at, updated_at) when you click Deploy. The Migrate action updates that row to a different host.

To run the same compose in multiple places, create separately-named stacks:

  • stacks/analytics-eu/compose.yaml (deployed on host prod-eu)
  • stacks/analytics-us/compose.yaml (deployed on host prod-us)

Each is a distinct stack with its own deployment row, audit trail, and scaling rules.

Clicking Deploy runs the equivalent of:

Terminal window
docker compose -f compose.yaml -p <project> up -d --remove-orphans

This is idempotent:

  • No changes → no containers recreated
  • Image tag bump → affected containers recreated
  • Volume added → created
  • Volume removed → not automatically removed (safety; use Prune to reclaim)

A stack is always in exactly one of:

StateMeaning
not_deployedCompose file on disk, never deployed
runningAll services running and healthy
partialSome services running, some not
stoppedExplicitly stopped via dockmesh or docker compose stop
errorLast deploy failed
changes_pendingcompose.yaml or env on disk differ from last deploy

State is computed from Docker’s reported container states (live, per host) combined with the last-deploy metadata in the stack_deployments table. Persistent per-stack settings like scaling rules and environment overlays live in .dockmesh.meta.json inside the stack’s directory.

A service = a declared container template in compose. Services can scale to N containers (via Scaling):

  • service: web, replicas: 3 → 3 containers named myapp_web_1, myapp_web_2, myapp_web_3
  • Each gets the same image, env, volumes (except for container-scoped ones)
  • Load-balanced by Docker’s built-in DNS round-robin or by an external LB

When you deploy a stack, Compose creates a Docker network named <project>_default. All services in the stack join it. They can reach each other by service name.

Other stacks can’t reach this network unless you explicitly declare it as external: true in both stacks’ compose files.

This is the inverse of Kubernetes’ default: in K8s everything is reachable, and you add NetworkPolicies to restrict. In dockmesh/Compose, nothing is reachable cross-stack unless you arrange it.

Same pattern. volumes: pgdata: in compose creates a Docker volume named <project>_pgdata. It’s local to the stack unless declared external: true.

Cross-stack volume sharing is possible but unusual — usually you share data via networked APIs instead.

Why not “services” (Docker Swarm concept)?

Section titled “Why not “services” (Docker Swarm concept)?”

Docker Swarm’s “service” is a superset of a Compose service — adds replicas, constraints, rolling updates, health-based restarts. dockmesh uses plain Compose + a coordinator on top:

  • Replicas via dockmesh Scaling (which does what Swarm does, but cross-host-aware)
  • Constraints via host tags
  • Rolling updates via Stack Migration
  • Health-based restart via Compose’s healthcheck: + Docker’s auto-restart

Same capabilities, simpler runtime.

Kubernetes is more powerful and more complex. dockmesh is for the sweet spot between “one Docker host” and “I really need Kubernetes”:

  • 10-500 containers
  • Compose-shaped apps
  • Team size < 20 engineers managing it
  • Homelabs, small SaaS, internal tools

If your needs outgrow dockmesh: plan a migration to K8s. dockmesh doesn’t pretend to replace it.