Skip to content

Commit 0cb589f

Browse files
committed
docs: add README with setup instructions and Windows integration guide
1 parent 785fef5 commit 0cb589f

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

README.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
```
2+
_
3+
__| | _____ ___ __ _ __ _____ ___ _
4+
/ _` |/ _ \ \ / / '_ \| '__/ _ \ \/ / | | |
5+
| (_| | __/\ V /| |_) | | | (_) > <| |_| |
6+
\__,_|\___| \_/ | .__/|_| \___/_/\_\\__, |
7+
|_| |___/
8+
```
9+
10+
Automatic Docker port conflict resolution via per-project loopback IPs.
11+
12+
## The Problem
13+
14+
When running multiple Docker Compose projects simultaneously, services like PostgreSQL and Redis bind to the same default host ports (5432, 6379), causing conflicts. You end up remapping ports in every `docker-compose.yml` and trying to remember which port belongs to which project.
15+
16+
## How It Works
17+
18+
devproxy assigns a unique loopback IP to each Docker Compose project and forwards TCP traffic on standard ports:
19+
20+
```
21+
acme.localhost:5432 -> acme's PostgreSQL
22+
acme.localhost:6379 -> acme's Redis
23+
widgets.localhost:5432 -> widgets's PostgreSQL
24+
widgets.localhost:6379 -> widgets's Redis
25+
```
26+
27+
No changes to your existing docker-compose files. Each project uses a memorable, consistent endpoint (`project.localhost:standard-port`) regardless of what host port Docker assigns.
28+
29+
## Quick Start (NixOS)
30+
31+
The recommended way to run devproxy is via the NixOS module:
32+
33+
```nix
34+
{
35+
inputs.devproxy.url = "github:alysnnix/devproxy";
36+
37+
# In your host configuration:
38+
imports = [ inputs.devproxy.nixosModules.default ];
39+
services.devproxy.enable = true;
40+
}
41+
```
42+
43+
The module automatically configures systemd-resolved DNS delegation, capabilities, and the systemd service.
44+
45+
## Quick Start (Manual)
46+
47+
Build from source and run:
48+
49+
```bash
50+
go build -o devproxy ./cmd/devproxy
51+
sudo mkdir -p /run/devproxy
52+
sudo ./devproxy daemon
53+
```
54+
55+
You will also need to configure systemd-resolved to delegate `.localhost` queries to devproxy. Add to your `resolved.conf`:
56+
57+
```ini
58+
[Resolve]
59+
DNS=127.0.53.53
60+
Domains=~localhost
61+
```
62+
63+
The daemon requires `CAP_NET_ADMIN` (loopback IP management) and `CAP_NET_BIND_SERVICE` (DNS on port 53).
64+
65+
## CLI Commands
66+
67+
| Command | Description |
68+
|---|---|
69+
| `devproxy daemon` | Run the daemon (normally started via systemd) |
70+
| `devproxy status` | List active projects, IPs, and port mappings (`--json` for machine-readable output) |
71+
| `devproxy down` | Stop daemon and clean up all state |
72+
| `devproxy cleanup` | Purge stale state (loopback IPs, DNS) without starting the daemon |
73+
| `devproxy doctor` | Validate the entire chain: Docker socket, DNS delegation, systemd-resolved, loopback IPs |
74+
| `devproxy windows-setup` | Generate PowerShell script for Windows integration |
75+
| `devproxy windows-cleanup` | Generate PowerShell script to remove all Windows-side configuration |
76+
77+
## Windows Integration
78+
79+
Windows-native apps (DBeaver, Chrome, etc.) cannot access WSL2 loopback IPs. devproxy provides a Windows integration path using a Microsoft KM-TEST Loopback Adapter with `netsh portproxy` forwarding.
80+
81+
### One-Time Setup
82+
83+
1. **Install the loopback adapter:**
84+
Open Device Manager -> Add legacy hardware -> Network adapters -> Microsoft -> KM-TEST Loopback Adapter
85+
86+
2. **Find the adapter name:**
87+
```powershell
88+
Get-NetAdapter | Where-Object { $_.InterfaceDescription -like '*Loopback*' }
89+
```
90+
91+
3. **Add project IPs to the adapter:**
92+
```powershell
93+
New-NetIPAddress -InterfaceAlias "YOUR_ADAPTER_NAME" -IPAddress 10.42.x.y -PrefixLength 32
94+
```
95+
96+
4. **Generate the full setup script:**
97+
Run `devproxy windows-setup` in WSL2, then execute the generated PowerShell script as Administrator. This creates portproxy rules, adds IPs, and updates the Windows hosts file.
98+
99+
After setup, `acme.localhost:5432` works identically in DBeaver on Windows and psql in WSL2.
100+
101+
### Maintenance
102+
103+
Re-run `devproxy windows-setup` when:
104+
105+
- New projects are added or removed
106+
- Containers restart (Docker may assign new host ports)
107+
- WSL2 reboots (the eth0 IP changes)
108+
109+
The generated script is idempotent -- it cleans up old rules before creating new ones.
110+
111+
Run `devproxy windows-cleanup` to remove everything: adapter IPs, portproxy rules, and hosts file entries.
112+
113+
## Architecture
114+
115+
```
116+
Docker Socket --> devproxy daemon --> 1. Assign loopback IP (127.X.Y.1)
117+
2. Register in embedded DNS
118+
3. Start TCP forwarder (if needed)
119+
```
120+
121+
**Components:**
122+
123+
- **Docker Watcher** -- monitors container start/die events via the Docker socket, extracts Compose project names and port mappings
124+
- **IP Manager** -- deterministic IP assignment from project name using FNV-1a hash (range: `127.10.1.1` -- `127.254.254.1`, ~62k slots)
125+
- **DNS Resolver** -- embedded DNS server on `127.0.53.53:53` resolving `*.localhost` to project IPs
126+
- **TCP Forwarder** -- pure Go TCP forwarding from `project-ip:container-port` to `127.0.0.1:docker-host-port`
127+
- **State** -- in-memory project state, rebuilt from running containers on startup
128+
129+
CLI commands communicate with the daemon via HTTP over a Unix socket at `/run/devproxy/devproxy.sock`.
130+
131+
## How DNS Works
132+
133+
devproxy runs an embedded DNS server on `127.0.53.53:53`. Rather than editing `/etc/hosts` (which can be overwritten by NetworkManager, VPN scripts, or NixOS rebuilds), it uses systemd-resolved delegation:
134+
135+
```ini
136+
DNS=127.0.53.53
137+
Domains=~localhost
138+
```
139+
140+
This is surgical: **only** `*.localhost` queries go to devproxy. All other DNS traffic is completely unaffected. If devproxy crashes, only `.localhost` resolution breaks -- the rest of the system continues normally.
141+
142+
The `.localhost` TLD is used instead of `.local` because `.local` is reserved for mDNS (RFC 6762), while `.localhost` is guaranteed to resolve to loopback by RFC 6761.
143+
144+
## Development
145+
146+
```bash
147+
# Run tests
148+
go test ./internal/... ./cmd/... -v
149+
150+
# Integration tests (requires Docker + root)
151+
sudo go test -tags=integration ./integration/ -v
152+
153+
# Build with Nix
154+
nix build
155+
```
156+
157+
## License
158+
159+
MIT

0 commit comments

Comments
 (0)