BenchPress Documentation

BenchPress Documentation

Open in ChatGPT
Ask ChatGPT about this page
Open in Claude
Ask Claude about this page

Networking

Networking

Every address a bench answers on, and who can reach each one.

Who this is for. Somebody who cannot reach a bench, or who is changing how
one is addressed.

Before you start. A bench has up to four addresses at once. They are not
alternatives. Each one exists for a different caller, and three of the four are
private.

The four addresses

benchpress/addressing.py builds all of them in one place, and the app renders
what it returns rather than assembling a URL from a port it declared itself.

Address Form Reachable from
Public site https://<instance id>.<base domain> the internet, through Traefik
Public IDE https://ide-<instance id>.<base domain> the internet, through Traefik
Tunnel site http://<wg ip>:8000 a device on the VPN
Tunnel IDE http://<wg ip>:8080/ a device on the VPN

The two ports are properties of the bench image, not settings. They are
constants in addressing.py.

Port Serves
8000 the Frappe site
8080 code-server, the browser IDE

When a bench has no tunnel address the same helpers fall back to the container's
bridge address. When base_domain is empty or localhost, the two public
addresses do not exist and the helpers return nothing.

Bench bridges

A bench container joins a BenchPress bridge network, never the control plane's
own network.

Setting Default Means
bench_subnet_base 10.20 the first two octets of every bench bridge
bench_bridge_count 16 how many bridges may exist
bench_slots_per_bridge 1000 how many endpoints one bridge holds before the next is used

Bridge N is derived, not configured:

Property Value for index N
Network name benchpress-N
Bridge device bpbrN
Subnet <base>.<N × 16>.0/20
Gateway <base>.<N × 16>.1

The third octet steps by 16 so every /20 starts on its own boundary. A later
scheme giving each node a /16 renumbers nothing.

Choosing a bridge is advisory. placement.pick_network names the lowest
index with room. It takes no lock, and the count can go stale between the count
and the create. Docker is the arbiter. It refuses the create when the address
pool is empty, and the deploy rolls on to the next bridge.

A bridge is created when a bench actually reaches it. A Draft bench that is
never deployed costs no network.

Three infrastructure containers are attached to every bench bridge, so Docker's
embedded DNS answers in both directions: Traefik, the shared MariaDB and the
shared Redis. The control plane's own database and Redis are deliberately
absent.
A tenant bridge that reaches them is a breach, not a convenience.

The WireGuard plane

Every bench and every user device is a VPN Peer in the vpn_management app.
benchpress/vpn_adapter.py is the only seam between the two apps.

Peer Created by Key
A bench create_container_peer, at deploy step 5 generated locally, private half written into the container and stored nowhere else
A device register_device, from the Devices screen supplied by the device, or generated by the server

The insert claims the tunnel address atomically from the server's pool. Nothing
picks an address by hand.

A bench peer keeps a 25 second keepalive. A device peer is named
[Type] Name, which is how the adapter tells devices from bench containers in
one peer table.

Seven device types are accepted: Mobile, Laptop, Desktop, Tablet, Server, IoT
and Embedded. get_device_types returns the list so no screen hand-types it.

A bench created before the bridge families existed falls back to the legacy
gateway 172.30.0.1.

The public front door

benchpress/ingress.py owns every write to the Traefik route directory, and
that directory path lives in that module and nowhere else.

Traefik's file provider does not recurse, so the directory is flat. One file
holds one router for one bench, named by the instance id, which is always 32
hexadecimal characters.

Two files in that directory are never a bench's, and the convergence pass must
never delete them. They are named one by one rather than matched by shape.

File Owned by Deleting it
dynamic.yml the parent benchpress_devops repository takes the control plane off the internet
wildcard-anchor.yml ingress.ensure_anchor takes every bench certificate with it
<instance id>.yml one bench removes that bench's public address

A route file names the container, never an address. The container name is
the instance id, so no lifecycle transition can make the file stale.

Why bench routers name no certificate resolver

A bench router is written with tls: {}. That turns TLS on and names no
resolver, so the router serves whatever certificate the store already holds for
the requested name.

This is the point of the design. Let's Encrypt allows five certificates for each
identifier set every seven days. A router that asked for its own certificate
would cap bench churn at five a week.

ensure_anchor is what puts *.<base domain> in the store, and it is the only
place in this app that names a resolver. The anchor is written at deploy step 1,
minutes ahead of the image build, because DNS-01 has to finish issuing before a
bench route goes live.

The anchor is never deleted at teardown. It has to outlive every bench, because
it is what keeps the certificate renewing.

The IDE hostname is one label under the same base domain, so the same wildcard
covers it. Only the site hostname is checked when a deploy logs the certificate
state.

Who can reach what

Caller Public site Tunnel site SSH Control plane
The internet yes, if base_domain is set no no yes
A device on the VPN yes yes yes yes
Another bench on the same bridge no no no no
A bench, reaching the shared MariaDB the database only

No bench is on the public internet by port. The only public path is Traefik, and
it terminates TLS on the wildcard.

Verify

Read what a bench answers on:

bench --site <site> execute frappe.client.get_value \
  --kwargs "{'doctype':'Bench Instance','filters':{'name':'<id>'},'fieldname':['public_url','code_server_url','wg_ip','container_ip','bridge_network']}"

List the bridges Docker actually has:

docker network ls --filter name=benchpress-

Troubleshooting

Symptom Cause Fix
The public address returns 404 The bench has no route file The */5 reconcile pass rewrites it
The public address returns a TLS error The wildcard is not in the store Check wildcard-anchor.yml and the DNS-01 credentials
No public address at all base_domain is empty or localhost Set it. See Settings reference
The tunnel address does not answer The device is not on the VPN Connect over SSH and the VPN
A deploy fails at step 5 The worker has no WireGuard agent socket Run deploys on queue-long
A deploy fails to create a container Every bridge is full Raise bench_bridge_count
A bench reaches the control plane database A bridge was attached by hand Remove it. Only three infrastructure containers belong
Every bench lost its certificate The anchor file was deleted Redeploy any bench, or call ensure_anchor

Reference

Fact Value
Site port 8000
IDE port 8080
Default subnet base 10.20
Bridges 16
Slots for each bridge 1000
Subnet for bridge N <base>.<N × 16>.0/20
Bridge device bpbrN
Network name benchpress-N
Legacy gateway 172.30.0.1
Container keepalive 25 seconds
Device types 7
Route directory flat, one file for each bench
Protected route files dynamic.yml, wildcard-anchor.yml
Last updated 4 hours ago
Was this helpful?
Thanks!