Sandbox specs for iterating on the Online Boutique microservices against a real cluster without rebuilding images per change. Each spec puts exactly one service on your devbox while the rest of the stack stays in-cluster; traffic with the sandbox routing key lands on your local process.
- Cluster:
demo - Namespace:
microservices-demo
If those differ in your environment, edit the specs in dev/ before applying.
- Find your devbox ID:
signadot devbox list # or via MCP: list_devboxes - Apply the spec for the service you are changing:
signadot sandbox apply -f .signadot/dev/checkoutservice.yaml \ --set devbox-id=<devbox-id>
- Wait for
ready: trueand tunnelconnected: true:signadot sandbox get checkoutservice-dev
- Run the local service from the devbox (see per-service recipes below).
- Validate against
http://<svc>.microservices-demo.svc[:<port>]/...with thebaggage: sd-routing-key=<key>header.
All services read their downstream addresses from env vars. Resolve each
address to the FQDN that is in /etc/hosts on the devbox (<svc>.microservices-demo
or <svc>.microservices-demo.svc). Missing a downstream var does not fail at
startup — it fails on the first real request as a 500 or a timeout.
cd src/checkoutservice
go build -o /tmp/checkoutservice .
cat > /tmp/start_checkout.sh << 'EOF'
#!/bin/bash
export PORT=5050
export PRODUCT_CATALOG_SERVICE_ADDR=productcatalogservice.microservices-demo.svc:3550
export SHIPPING_SERVICE_ADDR=shippingservice.microservices-demo.svc:50051
export PAYMENT_SERVICE_ADDR=paymentservice.microservices-demo.svc:50051
export EMAIL_SERVICE_ADDR=emailservice.microservices-demo.svc:5000
export CURRENCY_SERVICE_ADDR=currencyservice.microservices-demo.svc:7000
export CART_SERVICE_ADDR=cartservice.microservices-demo.svc:7070
exec /tmp/checkoutservice
EOF
chmod +x /tmp/start_checkout.sh
fuser -k 5050/tcp 2>/dev/null || true
setsid /tmp/start_checkout.sh >> /tmp/checkoutservice.log 2>&1 &Note: the in-cluster Service exposes port 80, not 8080. Always curl
http://frontend.microservices-demo.svc/ (implicit :80). See "Known gotchas".
cd src/frontend
go build -o /tmp/frontend .
cat > /tmp/start_frontend.sh << 'EOF'
#!/bin/bash
export PORT=8080
export PRODUCT_CATALOG_SERVICE_ADDR=productcatalogservice.microservices-demo.svc:3550
export CURRENCY_SERVICE_ADDR=currencyservice.microservices-demo.svc:7000
export CART_SERVICE_ADDR=cartservice.microservices-demo.svc:7070
export RECOMMENDATION_SERVICE_ADDR=recommendationservice.microservices-demo.svc:8080
export SHIPPING_SERVICE_ADDR=shippingservice.microservices-demo.svc:50051
export CHECKOUT_SERVICE_ADDR=checkoutservice.microservices-demo.svc:5050
export AD_SERVICE_ADDR=adservice.microservices-demo.svc:9555
exec /tmp/frontend
EOF
chmod +x /tmp/start_frontend.sh
fuser -k 8080/tcp 2>/dev/null || true
setsid /tmp/start_frontend.sh >> /tmp/frontend.log 2>&1 &The shippingservice has no outbound service dependencies. It receives the cart
subtotal in USD directly in the GetQuoteRequest.cart_subtotal_usd field
(populated by checkoutservice) and applies the free-shipping threshold locally.
signadot sandbox apply -f .signadot/dev/shippingservice.yaml \
--set devbox-id=<devbox-id>
cd src/shippingservice
go build -o /tmp/shippingservice .
cat > /tmp/start_shipping.sh << 'EOF'
#!/bin/bash
export PORT=50051
export DISABLE_PROFILER=1
exec /tmp/shippingservice
EOF
chmod +x /tmp/start_shipping.sh
fuser -k 50051/tcp 2>/dev/null || true
setsid /tmp/start_shipping.sh >> /tmp/shippingservice.log 2>&1 &Validate with grpcurl from the devbox:
ROUTING_KEY=$(signadot sandbox get shippingservice-dev -o json | jq -r .routingKey)
grpcurl -plaintext \
-H "baggage: sd-routing-key=${ROUTING_KEY}" \
-import-path protos -proto demo.proto \
-d '{"address":{"city":"SF"},"items":[{"product_id":"1YMWWN1N4O","quantity":1}],
"cart_subtotal_usd":{"currency_code":"USD","units":"109","nanos":990000000}}' \
shippingservice.microservices-demo.svc:50051 \
hipstershop.ShippingService/GetQuote
# expect cost_usd.units = 0 (free shipping)-
Frontend Service port is 80, not 8080. The Dockerfile
EXPOSE 8080is the container port; the Kubernetes Service inkubernetes-manifests/frontend.yamlmaps:80 → :8080. Hithttp://frontend.microservices-demo.svc/(no port). Using:8080on the.svcURL returns an Envoy 503 that looks like a pod health issue but is a port mismatch. -
gRPC downstream addresses must be fully set. Missing a
*_SERVICE_ADDRenv var does not fail at startup — it fails on the first request that needs that downstream. Grep the service's Go source for*ServiceAddr/getEnvbefore starting to enumerate every required var. -
Baggage header propagation. Services built from this repo use an instrumented gRPC client and forward
baggageautomatically. If you add a new service or a handler that uses rawhttp.Client, baggage will be dropped and sandboxed downstreams will not fire.
From the devbox, drive the cluster .svc URL with the routing key header:
curl -s http://frontend.microservices-demo.svc/ \
-H "baggage: sd-routing-key=<routing-key>"Never validate against http://localhost:<port>/ — local loopback bypasses
cluster routing, so downstream calls from your local process go to the
cluster baseline without the routing key and sandboxed downstream services
never fire.