Cloud Run
sam-one serves its HTTP API, the console and the router’s WebSocket
transport on one port. That is the shape Cloud Run expects. This guide
deploys it there, joins nodes from anywhere, and lists what changes when
sam-one runs on Cloud Run. The same steps apply to any platform that
forwards HTTP and WebSockets to a container.
1. Build and push
PROJECT=my-project
REGION=us-central1
IMG=${REGION}-docker.pkg.dev/${PROJECT}/sam-mesh/sam-one:latest
docker build -f Dockerfile.sam-one -t "$IMG" .
gcloud auth configure-docker ${REGION}-docker.pkg.dev
docker push "$IMG"
2. Deploy
Cloud Run has no persistent disk, so the join and admin tokens are set through environment variables. Without this, a new instance would generate new tokens and nodes that enrolled earlier could not renew.
JOIN_TOKEN="sam_tok_$(openssl rand -hex 16)"
ADMIN_TOKEN="sam_adm_$(openssl rand -hex 16)"
gcloud run deploy sam-one \
--project "$PROJECT" --region "$REGION" \
--image "$IMG" \
--allow-unauthenticated \
--min-instances 1 --max-instances 1 \
--port 8080 \
--no-cpu-throttling \
--timeout 3600 \
--set-env-vars "SAM_TOKEN=${JOIN_TOKEN},SAM_ADMIN_TOKEN=${ADMIN_TOKEN}"
Each flag is needed:
--min-instances 1 --max-instances 1: the router’s DHT and relay state live in the one process. Two instances would be two separate meshes behind one URL.--no-cpu-throttling: the router runs background loops (lease renewal, key sync, DHT maintenance) between requests.--timeout 3600: Cloud Run limits the lifetime of a streaming request, and each node’s WebSocket connection is one. Nodes reconnect when the limit closes a connection, but a low value causes unnecessary reconnects.--allow-unauthenticated: the mesh authenticates its own callers. Cloud Run’s IAM check would block nodes before they could present a credential.
The router needs to know its public URL so that it can advertise an address that nodes can dial. The URL only exists after the first deploy:
URL=$(gcloud run services describe sam-one --project "$PROJECT" --region "$REGION" \
--format='value(status.url)')
gcloud run services update sam-one --project "$PROJECT" --region "$REGION" \
--update-env-vars "SAM_EXTERNAL_URL=${URL}"
3. Verify
curl -s "$URL/readyz" # 200
curl -s "$URL/info" | head -c 200 # contains /dns4/<host>/tcp/443/wss/p2p/<peer-id>
The console is at $URL/console. Use /readyz for probes. /healthz is
reserved by Cloud Run’s frontend on run.app domains and never reaches the
container.
4. Join nodes
echo -n "$JOIN_TOKEN" > join-token
sam-node run --control-plane "$URL" --bootstrap-token-path join-token
The node enrolls over HTTPS, reads the router’s wss address from /info,
and connects through the same port. Nodes behind NAT reach each other
through relay circuits on the Cloud Run instance, so neither node needs an
inbound port. Pass --announce-private=false to the nodes so that they do
not advertise LAN addresses that no remote peer can use.
Publishing and calling a service works as in
your own mesh: declare the service in
sam-node.yaml on one node, and call it through
/sam/<peer-id>/<type>/<name>/ on another.
5. Administer
sam-one is also the admin client. With SAM_ADMIN_TOKEN exported:
sam-one token create --server "$URL" --role sam:role:node --max-usages 1
sam-one token qr --server "$URL"
sam-one token list --server "$URL"
sam-one token revoke <token-id> --server "$URL"
sam-one admin ban <peer-id> --server "$URL"
What Cloud Run changes
- State is ephemeral. The SQLite database is in the container’s memory.
When an instance restarts, it forgets enrolled nodes and minted tokens,
and the router gets a new peer ID. Nodes read
/infoagain and re-enroll with the pinned join token. For state that survives restarts, point--db-driver postgres --db-dsn <dsn>at a managed database, or runsam-oneon a VM with a disk. - Rollouts overlap. During a deploy,
/infomay advertise the new instance while some WebSocket upgrades still reach the old one. Nodes detect the peer ID mismatch and retry. Joins succeed once the old revision has drained. - One source IP for everyone. All traffic arrives from a small number of
frontend proxies.
sam-oneraises libp2p’s per-source-IP connection limit for this automatically. A separatesam-routerbehind a proxy needs--conns-per-source-ipraised by hand. - Policy. First boot seeds the open development policy. Build a
--policy-fileinto the image or post a policy after the deploy, and run with--no-join-tokenonce you mint per-device tokens. The sam-one reference lists the flags.