cd /news/developer-tools/hybrid-container-governance-scaling-… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-54485] src=dev.to β†— pub= topic=developer-tools verified=true sentiment=↑ positive

Hybrid Container Governance: Scaling Patterns That Work

A developer built a hybrid container governance pattern to manage multiple WSL instances running the same MCP server stack. Instead of installing the stack on each instance, they containerized the entire environment using Docker, enabling a single deployment served to all WSL instances. The approach eliminated environmental drift and installation script debugging, shifting focus from deployment to feature development.

read5 min views1 publishedJul 10, 2026

The third WSL instance didn't need a reinstall. That's when I knew the pattern worked.

WSL 1 had the full stack β€” six ark-* MCP servers, oh-my-mcp gateway, server-commands-rtk, all configured, tested, running. WSL 2 was planned for the same setup. So was WSL 3. Each one would need npx @ev3lynx/oh-my-mcp

, the npm dependencies, the config files, the supergateway wrappers, the port allocations, the CLI bridge, the permission schema in opencode.jsonc.

I started scripting the install. Halfway through, I stopped and deleted the script.

Installing the same stack N times is the wrong answer. The right answer is one installation, served.

The architecture we'd built was already multi-process β€” oh-my-mcp managing six child servers, each with its own port, its own lifecycle, its own restart budget. The step from "multi-process on one machine" to "multi-WSL via one container" was smaller than expected:

FROM ghcr.io/anomalyco/opencode:dev-debian

RUN npm install -g @ev3lynx/oh-my-mcp

COPY supervisord.conf /etc/supervisor/conf.d/opencode-stack.conf

CMD ["supervisord", "-c", "/etc/supervisor/conf.d/opencode-stack.conf"]

The container runs two supervised processes:

opencode serve --hostname 0.0.0.0 --port 4096

oh-my-mcp --port 8090

(management API on 8080)Ports exposed: 4096 (OpenCode SSE), 8080 (management), 8090 (MCP gateway).

Volume mounts for persistence:

volumes:
  - ~/.config/opencode:/home/ev3lynx/.config/opencode
  - ~/.local/share/opencode:/home/ev3lynx/.local/share/opencode
  - ~/server:/home/ev3lynx/server
  - ~/.ssh:/home/ev3lynx/.ssh
  - /var/run/docker.sock:/var/run/docker.sock

From any WSL instance: docker exec -it opencode-stack opencode

. Same env, same config, same servers. Zero npm installs per distro.

But I didn't start with a container. I started the wrong way.

The first WSL instance got the full manual setup: npx @ev3lynx/oh-my-mcp

install, fnm multishell resolution, port allocation for each ark server, supergateway wrappers for every stdio→SSE bridge, systemd user service registration, and a fire-and-forget shell script that was already one edit away from breaking silently.

When WSL 2 needed the same thing, I ran the script again. It failed because systemctl --user

doesn't behave the same across WSL instances β€” Debian's systemd vs Alpine's OpenRC vs Ubuntu's snap-wrapped systemd. The paths were different. The fnm node version was different. The /run/user/1000/

socket path was absent on the second instance because the user ID was 1001.

I spent an afternoon fixing environmental drift between two WSL instances that were supposed to be identical. I caught myself debugging the wrong thing: not the architecture, not the code β€” but the installation path.

That was the moment the container decision made itself. Not because containers are trendy. Because I was debugging installation scripts instead of shipping features.

The tension isn't technical. The container is trivially simple β€” a Dockerfile, a docker-compose.yml, a volume mapping. The tension is architectural: we had built an MCP server ecosystem without a deployment story. Every server in the ark-* family was designed as a standalone process with clear boundaries (focused tools, independent lifecycle). But standalone doesn't mean deployable. A process without a reproducible environment is a test that passes on your machine.

Here's the voice that argues against it:

"It's three WSL instances. You can script the install. Docker adds overhead, a learning curve for the next person, another moving part. A Makefile and a README note is cheaper."

This voice isn't wrong β€” it's looking at the right cost but the wrong time horizon. At WSL 3, the Makefile is still fine. At WSL 5, when they're different distros (Debian, Ubuntu, Alpine, Fedora, Arch), each with different systemd quirks, different package managers, different default shells β€” the Makefile becomes a liability audit. At a CI runner and a team member's machine and a deployment target, it's a full-time maintenance job.

The cost of the container is paid once. The cost of environment-specific setup scripts compounds with every new target.

Six servers is a manageable number. Sixteen is not β€” unless you have a pattern that makes each one predictable.

Every ark-* server follows the same template:

Adding a new server is five files, not five hours:

server/ark-<name>/
β”œβ”€β”€ package.json         # @ev3lynx/ark-<name>
β”œβ”€β”€ tsconfig.json        # extends base
β”œβ”€β”€ src/server.ts        # tool handlers
└── .github/workflows/   # CI (ci.yml, publish.yml)

Build β†’ oh-my-mcp add --path server/ark-<name>

β†’ registered. No config file edit. No port conflict check. The gateway discovers it, assigns a port, and routes traffic.

The governance rule that handles 90% of decisions:

The container stack isn't just about deployment repeatability. It changes the capability surface entirely:

From any WSL instance: docker exec -it opencode-stack opencode

β€” same servers, same config, same memory graph. Sessions are portable across distros. Switch from Debian to Alpine to Arch without losing connection state.

From Windows: ark-delegator resolves via http://localhost:8106

β€” the port is mapped through Docker and accessible from the Windows host. The Windows OpenCode client sees the same MCP servers as every WSL instance. No network bridging. No WSL path translation.

From CI: The same container image. docker run opencode-stack opencode eval "run tests"

β€” CI gets the exact same tool set as interactive sessions. No drift between development and pipeline environments.

This is the pattern that survives: one image, many clients, zero per-target configuration.

The Makefile argument was right about the cost β€” just wrong about which cost compounds. The container is heavier on setup day. It's lighter on every subsequent day. And on the day WSL 5 shows up with a package manager you've never heard of, the container costs nothing at all.

The stack: oh-my-mcp gateway +

commands-rtk

CLI bridgeBuild tools that survive restarts. Build processes that survive machine boundaries. Build stacks that don't care which WSL distro the terminal opened today.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @wsl 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/hybrid-container-gov…] indexed:0 read:5min 2026-07-10 Β· β€”