Exposing ports
Access web servers and services running inside agent containers from your browser or local tools.
Single agent
1. Declare ports in agent.yaml
name: my-app
dependencies:
- node@20
ports:
web: 3000
command: ["npm", "start"]
The ports map defines endpoints. Each key is an endpoint name that becomes part of the URL.
2. Start the agent
$ moat run --name my-app ./my-app
Moat starts a routing proxy automatically when ports are configured.
3. Open in your browser
https://web.my-app.localhost:8080
The URL format is https://<endpoint>.<agent-name>.localhost:<proxy-port>. The proxy terminates TLS and forwards to the container’s port 3000.
Multiple ports
Declare as many endpoints as your application needs:
ports:
web: 3000
api: 8080
docs: 4000
Each gets its own URL:
https://web.my-app.localhost:8080 → container:3000
https://api.my-app.localhost:8080 → container:8080
https://docs.my-app.localhost:8080 → container:4000
Multiple agents
When two agents expose the same internal port, hostname routing keeps them separate. Each agent’s name creates a distinct URL namespace.
$ moat run --name dark-mode ./my-app &
$ moat run --name checkout ./my-app &
Both agents run web servers on port 3000, but each is accessible at its own hostname:
https://web.dark-mode.localhost:8080 → dark-mode container:3000
https://web.checkout.localhost:8080 → checkout container:3000
List running agents
$ moat list
NAME RUN ID STATE ENDPOINTS
dark-mode run_a1b2c3d4e5f6 running web
checkout run_d4e5f6a1b2c3 running web
Stop agents
By name:
$ moat stop dark-mode
By run ID:
$ moat stop run_a1b2c3d4e5f6
All at once:
$ moat stop --all
Trusting the CA certificate
The routing proxy serves HTTPS using a self-signed CA. Browsers show a certificate warning until you trust the CA. This is a one-time setup.
macOS:
$ sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
~/.moat/proxy/ca/ca.crt
Linux:
$ sudo cp ~/.moat/proxy/ca/ca.crt /usr/local/share/ca-certificates/moat.crt
$ sudo update-ca-certificates
Environment variables
Inside each container, MOAT_URL_* environment variables point to its own endpoints:
# In the dark-mode container:
MOAT_URL_WEB=http://web.dark-mode.localhost:8080
# In the checkout container:
MOAT_URL_WEB=http://web.checkout.localhost:8080
Use these for OAuth callback URLs, webhook endpoints, or inter-service communication within the agent.
Naming constraints
Agent names must be:
- Lowercase alphanumeric with hyphens
- Valid DNS labels (no underscores, no leading/trailing hyphens)
- Unique among running agents
The name comes from --name on the CLI or name in agent.yaml.
Proxy management
Check status
$ moat proxy status
Routing Proxy
=============
Status: running
Port: 8080
CA: ~/.moat/proxy/ca/ca.crt
Registered Agents:
dark-mode web:3000
checkout web:3000
Use a different port
$ moat proxy stop
$ moat proxy start --port 9000
URLs change accordingly: https://web.dark-mode.localhost:9000
You can also set MOAT_PROXY_PORT=9000 in your environment.
Use port 80 or 443
Privileged ports require sudo:
$ sudo moat proxy start --port 80
With port 80, URLs don’t need a port number: https://web.my-app.localhost
When a proxy is already running, moat run detects and uses it automatically.
Stop the proxy
$ moat proxy stop
The proxy stops automatically when all agents with ports exit. If you started it manually with moat proxy start, use moat proxy stop or Ctrl+C.
Without port access
Agents work without ports configured. They can still:
- Run code
- Make outbound HTTP requests
- Use credential injection
Port configuration is only needed when you want to access services running inside the container from outside.
Troubleshooting
”Name already in use”
An agent with that name is already running:
$ moat list
$ moat stop existing-agent
Browser shows certificate error
Trust the CA certificate (see Trusting the CA certificate), or re-trust if the certificate was regenerated:
# macOS
$ sudo security delete-certificate -c "Moat CA"
$ sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
~/.moat/proxy/ca/ca.crt
“Connection refused” on hostname
-
Check the proxy is running:
$ moat proxy status -
Check the agent is running:
$ moat list -
Verify the endpoint name matches
agent.yaml:# URL uses "web" but agent.yaml has "frontend" # Fix: use https://frontend.my-app.localhost:8080
Agents sharing a workspace
Each agent has an isolated container filesystem, but they share the same workspace directory on your host. Changes by one agent are visible to others.
For complete isolation, use moat wt to give each agent its own git worktree from the same repository:
$ moat wt dark-mode
$ moat wt checkout-fix
Each worktree gets its own branch and working directory at ~/.moat/worktrees/<repo-id>/<branch>, so agents never interfere with each other’s files. See moat wt for details.
Related
- Networking — Network policies, traffic flow, and proxy bypass rules
- Running Claude Code — Use port access with Claude Code sessions
- Snapshots — Independent snapshots per agent