COMPOSE_PROJECT_NAME · dedicated bind mounts and a port matrix · aligning env_file with in-container env
When you run two OpenClaw stacks in parallel on the same VPS, the usual failures are port collisions, default networks and volume names stepping on each other, and API keys visible on the host but missing inside the container env. This article uses a single-instance baseline vs minimal multi-instance diff set to line up COMPOSE_PROJECT_NAME, data directories, and a host port matrix, explains how env_file and environment are injected, and gives a six-step verification checklist plus conditions for rolling back to one stack. Read it together with the Docker Compose production baseline and Exit 137 triage and first-boot notes.
Duplicating docker-compose.yml and tweaking a few ports is not isolation: the default Compose project name keeps networks and anonymous volumes on a collision course; host-only exports do not enter containers automatically; and a reverse-proxy upstream can still point at an old container IP. When you see the signals below, return to the checklist and tick every row instead of stacking more containers.
Ports changed but COMPOSE_PROJECT_NAME did not: the default bridge network and internal DNS can still resolve another stack’s service names.
Overlapping data paths or symlinks: two ~/.openclaw trees or workspaces that share the same disk prefix let upgrade scripts overwrite each other.
API keys only in a shell profile: the docker compose up child process never sees the env_file path Compose expects.
Healthchecks too aggressive: the second stack cold-starts slower, gets restarted by mistake, and fights the first instance for locks.
Reverse proxy still targets 127.0.0.1 on an old port: when two gateways rotate, the edge still sends traffic to a stopped instance. Align upstreams using the reverse proxy and TLS article.
These fields are the usual minimal diff set for two stacks on one host. If you only spin up a short-lived trial stack, finish at least project name, data directory, host ports, and env_file paths before you automate anything. Resource caps and log rotation still follow the production baseline article.
| Field | Single-instance baseline | Required split for multi-instance | Typical pitfall |
|---|---|---|---|
COMPOSE_PROJECT_NAME | Default directory name | One short unique name per stack (for example oc_a / oc_b) | Renaming the folder but not the project name reuses anonymous volumes |
| Data bind paths | One host path | Fully separate trees such as /srv/openclaw-a and /srv/openclaw-b | Child symlinks that resolve to the same parent directory |
| Host ports | One mapping like 3000:3000 | Map to distinct host ports and record them in an ops port matrix | The second stack fails to bind while systemd keeps restarting it |
| Env injection | One .env | Per-stack files such as .env.oc-a with explicit env_file in Compose | Host exports never land in Compose’s substitution scope |
| Healthchecks | One start_period | Raise start_period and retries when cold start is slower | Both stacks restart together and spike CPU |
Project name and data directory come before the port matrix; reversing that order lets the first upgrade script write both stacks under one prefix.
These steps assume you can already boot the official or team Compose snippet on a single machine; the goal is to make the second stack’s rendered config and runtime env auditable side by side.
Set project name and directories: export COMPOSE_PROJECT_NAME, create a dedicated data tree, and confirm there are no nested symlinks into the other stack.
Freeze the port matrix: assign free host ports for the gateway, control UI, and any extra channel ports, and write them into your runbook.
Split env_file: one secrets file path per stack instead of sharing .env; use docker compose --project-directory so the working directory is explicit.
Render and diff: run docker compose -f ... config for both stacks and confirm networks, volumes, and ports no longer show duplicate bind mounts or clashing names.
Runtime check: use docker compose exec to print the variables you care about inside the container and confirm they match masked checks you run on the host.
Health and logs: set start_period and json-file caps per the baseline article so two stacks cannot fill the disk at once.
export COMPOSE_PROJECT_NAME=oc_b docker compose --env-file ./.env.oc-b -f docker-compose.yml config > /tmp/oc-b.rendered.yml docker compose --env-file ./.env.oc-b -f docker-compose.yml up -d docker compose --env-file ./.env.oc-b exec -T openclaw-gateway sh -lc 'env | grep -E "ANTHROPIC|OPENAI" | sed "s/=.*/=***MASK***/"'
Tip: the config subcommand only resolves the merged file and does not start containers; save the rendered diff before up so you can compare stacks.
Treat the items below as a starting point for on-call and postmortems; replace thresholds with values from your real Compose files and host telemetry, and do not present them as an external SLA. When traffic “sometimes hits the wrong instance,” capture rendered configs for both stacks, container inspect network segments, and reverse-proxy upstream snapshots in the same ticket.
up, run ss -lntp or an equivalent and attach the output.json-file writers often exhausts inodes and bandwidth before CPU; follow the inspection cadence in the baseline article.| Symptom | Suspect fields first | Commands or evidence |
|---|---|---|
| No API key inside the container | env_file path, Compose working directory | docker compose config plus exec env side by side |
| Healthcheck thrash and restart loops | start_period, CPU steal | Host dmesg / cgroup stats aligned with container log timelines |
| Requests land on the wrong instance | Reverse-proxy upstream, stale containers | docker ps -a and upstream file diffs before and after reload |
Warning: overwriting a shared .env path before the first stack stops scrambles key rotation order across both stacks; always copy a new file first, then switch references.
Squeezing two production-grade OpenClaw stacks onto one small-memory VPS usually fails on disk and logs before raw CPU; without separate data directories and a port matrix, triage devolves into guessing which gateway you reached. Editing Compose by hand without keeping rendered diffs also makes upgrades impossible to attribute (“who changed which env line?”).
Teams that need a stable host, predictable bandwidth, and separable working directories often run OpenClaw on an orderable cloud Mac or a dedicated VPS plan so this checklist stays enforceable. When you want multi-instance experiments and production isolation in the same operational story, VpsMesh cloud Mac Mini rental is usually the better fit: roles can be split per node, paths audited end to end, and the same Compose diff set reviewed alongside queue policy.
Work through the article’s table and confirm anonymous volumes, default network names, host ports, and env_file do not still overlap the first stack; renaming the directory without the project name is the most common miss. For more on healthchecks and restart policy, see the Docker Compose production baseline article.
Compose likely did not load the right env_file, the working directory differs, or variables exist only under build; follow the six steps using docker compose config and exec. For first-boot env issues, read the environment section in the Exit 137 triage article.
Run docker compose down on the secondary stack, confirm ports are free, back up both data directories, and avoid deleting volumes before you export configs. For plans and ordering, read the pricing page and order page; for access policy, use the help center.