@studiometa/trafic-agent
v0.1.41
Published
Server agent for DDEV preview environments — auth, scale-to-zero, auto-start
Downloads
691
Readme
@studiometa/trafic-agent
Server agent for DDEV preview environments — auth, scale-to-zero, and auto-start.
Part of Trafic, a tool for managing DDEV preview environments on Linux servers.
Features
- Forward auth — Traefik middleware for IP whitelist, basic auth, and token auth
- Scale-to-zero — Automatically stop idle DDEV projects to save RAM
- Auto-start — Show a waiting page and restart stopped projects on request
- Per-project config — Override auth and idle timeout per project
Installation
npm install -g @studiometa/trafic-agentCommands
trafic-agent serve
Start the agent server.
trafic-agent serve
# or with custom config
trafic-agent serve --config /etc/trafic/config.tomltrafic-agent setup
Interactive server setup — installs Docker, DDEV, configures DNS, and sets up the agent as a systemd service.
trafic-agent setup --tld previews.example.comRequirements:
- Ubuntu 24.04 LTS
- Root access (for initial setup)
- Wildcard DNS pointing to the server
trafic-agent upgrade / trafic-agent update
Upgrade the server to the latest version of trafic-agent in one command. update is an alias for upgrade.
Steps:
- Check for updates — queries the npm registry for the latest version
- Install — runs
npm install -g @studiometa/trafic-agent@latestif a newer version is available - Migrations — runs any pending server migrations (forward-only, idempotent)
- Restart — restarts the
trafic-agentsystemd service
Fresh servers set up with trafic-agent setup have all migrations automatically marked as applied, so migrations only run when needed on existing deployments.
# Upgrade to the latest version (recommended)
sudo trafic-agent upgrade
# Preview what would be done without making changes
sudo trafic-agent upgrade --dry-run
# List all migrations and their status (no install or restart)
trafic-agent upgrade --listExample --list output:
✓ 0001__ddev_apt_repo Migrate DDEV from manual tarball to apt repository (applied)
✓ 0002__mkcert_ddev_user Install mkcert CA in the ddev user trust store (applied)Migration state is stored in /etc/trafic/.migrations.json and updated after each individual migration, so a partial failure leaves the state consistent.
Configuration
Create /etc/trafic/config.toml:
# Required: TLD for DDEV projects
tld = "previews.example.com"
# Agent HTTP server port (default: 9876)
port = 9876
# Scale-to-zero: stop idle projects after this duration
idle_timeout = "4h"
# Authentication
[auth]
default_policy = "basic" # allow, deny, basic, or token
# Proxies in front of the agent (default: 1 = ddev-router/Traefik alone).
# Raise by one for each extra proxy: a CDN or load balancer ahead of
# Traefik makes it 2. Set this before using allowed_ips — it decides which
# X-Forwarded-For entry is treated as the client address.
trusted_proxy_hops = 1
# IP whitelist (bypasses auth)
allowed_ips = ["192.168.1.0/24", "10.0.0.0/8"]
# Bearer tokens for CI/API access
tokens = ["your-ci-token"]
# Basic auth credentials
basic_auth = ["user:password"]
# Per-hostname rules
[[auth.rules]]
match = "*.public.*"
policy = "allow"
[[auth.rules]]
match = "admin.*"
policy = "basic"Per-project configuration
Create .ddev/config.trafic.yaml in your project:
# Override auth policy for this project
auth_policy: allow # allow, deny, basic, or token
# Override idle timeout (or disable with "never")
idle_timeout: neverHow it works
HTTPS request
│
▼
Traefik (DDEV router)
│
├─► forwardAuth → trafic-agent
│ │
│ ├─► 200 OK → DDEV project
│ └─► 401 → Basic auth prompt
│
└─► 502 error → errors middleware → trafic-agent
│
├─► known project → Waiting page + auto-start
└─► unknown → Error pageThe agent:
- Handles forward auth requests from Traefik
- Checks IP whitelist, tokens, or basic auth
- Tracks project activity for scale-to-zero
- Starts stopped projects when requested
- Shows a waiting page while projects start
Endpoints
All internal endpoints are prefixed with __ so they cannot collide with a
project's own paths. Everything else is treated as a request for a project.
| Endpoint | Description |
|----------|-------------|
| GET /__auth__ | Forward auth for Traefik. 200 allows, 401 prompts for basic auth |
| GET /__status__?project=<name> | Project status as JSON, for the waiting page to poll |
| GET /__health__ | Agent health and version |
| anything else | Waiting page for a known project, error page otherwise |
TLS
Traefik obtains the certificates, and setup --email is what turns that on:
trafic setup --host server.example.com --tld previews.example.com \
--email [email protected]With an email, DDEV sets use_letsencrypt=true and Traefik requests a
certificate per project hostname on first use — previews included, since each
one gets its own router. Without an email, projects are served with a locally
trusted mkcert certificate, which is fine behind a proxy that terminates TLS
itself but shows a browser warning if used directly.
Two limits worth knowing before you rely on it:
- Let's Encrypt allows 50 new certificates per registered domain per week. Each preview hostname is a new certificate, so environments that churn fast can hit the ceiling. Renewals do not count against it.
- Turning Let's Encrypt off does not discard certificates already issued.
Traefik keeps them in
acme.jsonin theddev-global-cachevolume and serves them again after a restart, soconfigureDdevdeletes that storage when Let's Encrypt is disabled.
Wildcard certificates would avoid the per-hostname limit but need a DNS-01 challenge, which DDEV does not do. If you churn tens of previews a week, put a proxy in front that can (Caddy or Traefik with a DNS provider) and disable Let's Encrypt in DDEV so the two do not both try.
Network exposure
ufw default deny incoming does not cover the ports Docker publishes.
Docker adds its rules to nat/PREROUTING and the DOCKER chain of FORWARD,
both of which are evaluated before UFW's chains. So the ports ddev-router
publishes are reachable from the internet whatever UFW says about them, and
ufw status will not tell you otherwise.
setup opens 22, 80 and 443, and 9876 for the agent from the Docker bridge
only. Those rules are accurate. What they do not do is close the ports
ddev-router publishes for DDEV's tools:
| Port | Service | |------|---------| | 8025, 8026 | Mailpit | | 8142, 8143 | xhgui |
Both layers now apply:
DOCKER-USERrules, installed bysetup. That is the chain Docker jumps to first inFORWARDand never flushes, so it is the only host-level hook that can filter a published port. The rules drop traffic to the tool ports unless it comes from Docker's own networks. They match on the original destination port (-m conntrack --ctorigdstport), because byFORWARDthe destination has already been rewritten to the container.They live in
/usr/local/sbin/trafic-docker-firewall, reapplied at boot and after any Docker restart bytrafic-docker-firewall.service— Docker recreatesDOCKER-USERempty and nothing else would restore them. They will not appear inufw status. Inspect them withiptables -S DOCKER-USER.Forward auth, attached to every entry point ddev-router publishes, so a request that does reach a tool port still needs credentials. An earlier version attached it only to 80 and 443, which left xhgui answering from the internet with no authentication at all.
Access from the host itself is unaffected: a connection to 127.0.0.1:8026
goes through Docker's userland proxy rather than FORWARD, so an SSH tunnel
still works:
ssh -L 8026:127.0.0.1:8026 [email protected]
# then open https://localhost:8026A provider firewall is not a substitute
Recommended as an extra layer, but do not rely on it alone. Measured on an OVH dedicated server with the Edge Firewall enabled and a correct rule denying these ports: a connection from another host inside OVH still completed in 113ms, while a port with nothing listening was dropped and timed out at 12s. The deny applies to traffic crossing the provider's edge; traffic that never crosses it is not filtered.
Anyone able to rent a VM from the same provider is inside that blind spot,
which for a large host is a very low bar. That is why the DOCKER-USER rules
above exist: they apply to every packet regardless of origin.
On OVH the setting is the Network Firewall in the control panel. Two things to
know: it is stateless, so permit tcp established is required or return
traffic for outbound connections is dropped, and it is IPv4-only. setup does
not configure it — it can neither create nor verify it.
Binding ddev-router to loopback only (router-bind-all-interfaces=false)
closes everything at once, but then nothing serves the public and you need a
host proxy in front. That is a reasonable setup; it is just a different one
from what setup builds.
License
MIT — see LICENSE
