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.
A stack = a Compose project
Section titled “A stack = a Compose project”Every stack in dockmesh corresponds to one docker compose project:
- One
compose.yamlfile - 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.
The filesystem is the source of truth
Section titled “The filesystem is the source of truth”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.jsonnext 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
Stacks bind to a host on deploy
Section titled “Stacks bind to a host on deploy”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 hostprod-eu)stacks/analytics-us/compose.yaml(deployed on hostprod-us)
Each is a distinct stack with its own deployment row, audit trail, and scaling rules.
Deploy is idempotent
Section titled “Deploy is idempotent”Clicking Deploy runs the equivalent of:
docker compose -f compose.yaml -p <project> up -d --remove-orphansThis 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)
The state model
Section titled “The state model”A stack is always in exactly one of:
| State | Meaning |
|---|---|
not_deployed | Compose file on disk, never deployed |
running | All services running and healthy |
partial | Some services running, some not |
stopped | Explicitly stopped via dockmesh or docker compose stop |
error | Last deploy failed |
changes_pending | compose.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.
Services within a stack
Section titled “Services within a stack”A service = a declared container template in compose. Services can scale to N containers (via Scaling):
service: web, replicas: 3→ 3 containers namedmyapp_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
Networks: stack-local by default
Section titled “Networks: stack-local by default”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.
Volumes: stack-local by default
Section titled “Volumes: stack-local by default”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.
Why not Kubernetes?
Section titled “Why not Kubernetes?”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.
See also
Section titled “See also”- Stack Management — the UI operations
- Agent Protocol — how multi-host works
- Host Tags — the tagging mental model