Once I had Weave running across my AWS hosts, the next question I had to answer was a practical one: how do I actually run real, multi-service applications on top of it? Peering routers is only step one — the day-to-day work is attaching containers, discovering services by name, load balancing between replicas, and making sure the network survives restarts and scaling events. This article covers all of that.
A Quick Recap of the Model
Docker’s default networking is host-scoped: every container gets an IP on the host’s private docker0 bridge, and that IP means nothing to any other host. Weave replaces this mental model with a single flat network address space shared by every container on every participating host. From the container’s point of view, there’s no difference between talking to a container on the same machine and a container three hosts away — the Weave router handles routing and encapsulation transparently.
This matters because it lets you write application configuration (connection strings, service URLs) using stable names instead of juggling host IPs and port mappings, which is exactly how service discovery works in more complex orchestrators like Kubernetes or Swarm, just at a simpler scale.
Attaching Containers to Weave
There are two ways to get a container onto the Weave network.
Method 1: weave env and docker run
eval "$(weave env)"
docker run -d --name redis redis:7-alpine
Every container launched in a shell where weave env has been evaluated is automatically attached, because Weave rewrites DOCKER_HOST to point at a proxy that intercepts docker run calls and injects the network attachment.
To go back to normal Docker networking in that shell:
eval "$(weave env --restore)"
Method 2: weave attach (attach an already-running container)
If a container is already running on the default Docker bridge and I want to add it to Weave without restarting it:
docker run -d --name worker myorg/worker:latest
weave attach worker
weave attach returns the newly assigned Weave IP:
10.40.0.5
I use weave detach worker to remove it from the overlay again if needed.
Naming and Service Discovery
Weave includes a built-in DNS server (weaveDNS) that automatically registers every attached container under <container-name>.weave.local.
docker run -d --name api myorg/api:latest
docker run -it --rm busybox nslookup api.weave.local
Expected output:
Server: 172.17.0.1
Address: 172.17.0.1:53
Name: api.weave.local
Address 1: 10.40.0.7 api
If I run multiple replicas of the same service under the same name, weaveDNS automatically load-balances DNS responses across all of them in round-robin fashion:
docker run -d --name api myorg/api:latest
docker run -d --name api myorg/api:latest
docker run -d --name api myorg/api:latest
Now nslookup api.weave.local returns three A records, and each new TCP connection from a client will typically hit a different backend depending on client-side DNS caching behavior.
Running a Real Multi-Host App: A Practical Example
Let’s say I’m deploying a small web app with a frontend, an API, and a Redis cache, split across two AWS hosts.
On host1 (runs Redis and the API):
eval "$(weave env)"
docker run -d --name redis redis:7-alpine
docker run -d --name api -e REDIS_HOST=redis.weave.local myorg/api:latest
On host2 (runs the frontend):
eval "$(weave env)"
docker run -d --name frontend -e API_URL=http://api.weave.local:8080 -p 80:80 myorg/frontend:latest
The frontend container on host2 reaches the API container on host1 purely through the DNS name api.weave.local — no hardcoded IPs, no manually published ports between hosts, and no VPC route table changes required beyond allowing Weave’s control-plane ports.
Using Weave with Docker Compose Across Hosts
Compose is inherently single-host by default, but I can still use it per-host and rely on Weave to bridge them. Here’s the Compose file I run on host1:
version: "3.9"
services:
redis:
image: redis:7-alpine
container_name: redis
api:
image: myorg/api:latest
container_name: api
environment:
- REDIS_HOST=redis.weave.local
depends_on:
- redis
And on host2:
version: "3.9"
services:
frontend:
image: myorg/frontend:latest
container_name: frontend
environment:
- API_URL=http://api.weave.local:8080
ports:
- "80:80"
I bring each up with:
eval "$(weave env)"
docker compose up -d
Because weave env reroutes the Docker CLI/Compose calls through the Weave proxy, containers created by Compose land on the overlay just like containers created by plain docker run.
Inspecting the Network
A few commands I use to understand what’s actually attached:
weave ps # list all containers on the Weave network with IP/MAC
weave dns-lookup api # test DNS resolution manually
weave status targets # peers Weave is trying to connect to
Sample weave ps output:
weave:expose ae:14:8f:00:00:01 10.40.0.1/12
redis 7e:2b:91:aa:00:02 10.40.0.2/12
api 1c:44:5d:bb:00:03 10.40.0.3/12
frontend 92:0a:6c:cc:00:04 10.40.0.4/12
Isolation with Sub-Networks
If I need to segment traffic — say, keeping a database tier reachable only by the API tier — Weave supports IP range allocation per application:
weave launch --ipalloc-range 10.32.0.0/12 --ipalloc-default-subnet 10.32.0.0/16
docker run -d --name db --net=weave --ip 10.32.5.10 myorg/postgres:latest
Combined with standard iptables rules on each host (Weave itself doesn’t enforce firewalling by application identity the way a Kubernetes NetworkPolicy does), this gives coarse-grained isolation.
Troubleshooting Common Issues
- Containers can’t resolve
.weave.localnames: confirmweaveDNSis running withweave status dns; it’s enabled by default but can be disabled with--no-dnsat launch. weave envdoesn’t seem to apply: this sets shell environment variables, so it only affects the current shell session — remember to re-run it in every new terminal or script context, or bake it into your deployment scripts.- Stale entries after a container is removed: Weave automatically deregisters DNS entries when a container stops, but if a host crashes ungracefully, run
weave status dnsto check for orphaned records andweave forget <ip>if needed.
Best Practices
- Use container names deliberately, since they become your service discovery keys — treat them the way you’d treat hostnames in production DNS.
- Keep
weave envcalls inside your deployment scripts (Ansible, systemd unitExecStartPre, CI/CD pipeline) rather than typing them manually, so environments stay reproducible. - Combine Weave with a health-check sidecar or your app’s own readiness endpoint, since weaveDNS load balancing is naive round-robin and doesn’t check backend health.
- For anything beyond a handful of hosts, evaluate whether you actually need full Kubernetes with a proper CNI instead of hand-rolled Weave + Compose, since Weave’s Kubernetes-native mode gives you NetworkPolicy support that plain Docker Weave lacks.
Scaling to a Larger Fleet
Everything above works fine with two or three hosts, but once I started running Weave across a dozen or more EC2 instances, a few extra considerations came up that are worth knowing before you scale.
Peer discovery at scale. Rather than hardcoding every peer’s IP into every weave launch command, I use a small bootstrap pattern: each new host only needs to know about one existing peer, and Weave’s gossip protocol handles discovering the rest of the mesh automatically.
# On a brand new host joining an existing 10-node cluster
weave launch 10.0.1.10
Weave will learn about the other nine peers from 10.0.1.10 within a few gossip rounds, which I can confirm with:
weave status peers
Restarting Weave safely. If I need to restart the Weave router on a host (say, after an upgrade), running containers keep their existing network state, but new connections briefly pause:
weave stop
weave launch <peer-ip>
I always script this one host at a time, never all hosts simultaneously, so the gossip mesh never loses quorum entirely.
Resource footprint. The Weave router itself is a lightweight Go binary running as a container, typically consuming under 50MB of RAM at idle and scaling modestly with the number of peers and attached containers — small enough that I don’t think twice about running it alongside application containers even on smaller t3.medium instances.
A Real-World Deployment Pattern
In one project, I used exactly this setup to run a staging environment that mirrored production topology across three AWS Availability Zones without provisioning a managed Kubernetes cluster just for staging. Each AZ had one EC2 host running a slice of the application (frontend, API, worker, cache), all peered over Weave, with weave dns-lookup used inside health-check scripts to confirm a given service was actually reachable from every AZ before promoting a deployment. It was a pragmatic middle ground — much simpler to operate than EKS for a small staging fleet, while still exercising real cross-host, cross-AZ networking behavior before code shipped to the production cluster.
Summary
Running Docker containers on a Weave network turns a set of independent Docker hosts into something that behaves like a single logical cluster for networking purposes. Containers get real, routable IPs, name-based discovery works out of the box through weaveDNS, and Compose files translate almost unchanged from single-host to multi-host deployments. It’s a lightweight way to get multi-host container networking without adopting a full orchestrator, and it’s a good stepping stone for understanding the service discovery patterns you’ll meet again in Kubernetes.
References
- Docker container networking: https://docs.docker.com/network/
- Docker Compose file reference: https://docs.docker.com/compose/compose-file/
- Weave Net documentation (archived): https://www.weave.works/docs/net/latest/overview/
- Kubernetes Service and DNS concepts: https://kubernetes.io/docs/concepts/services-networking/service/