gobridge

Deployment Guide

This guide covers deployment topology, configuration delivery, secret management, networking, health checks, observability, and scaling. The concepts apply wherever you run GoBridge; the concrete artifact differs by how you build it. For cloud-specific guidance, see What’s Next.

The AWS deployment profile is not a platform-neutral image. The project publishes no image; on AWS the CDK constructs build one into your own account at deploy time (and the repository root Dockerfile builds the same thing locally — see Container and Orchestrator Deployment). That image runs deployment/aws and is bound to AWS. It requires SSM to resolve secrets — admin_api_key_param is mandatory (deployment/aws/lib/model/bootstrap.go), the SSM resolver runs at startup (deployment/aws/lib/bootstrap/secrets.go), and it builds a DynamoDB client unconditionally (deployment/aws/lib/bootstrap/app.go). On Kubernetes and other non-AWS platforms run the maintained Kubernetes profile, which packages the reference binary (MQTT transport, memory/SQLite stores, file:// credentials, API keys from a Secret) with explicit GO_BUILD_TAGS=gobridge_mqtt,gobridge_native and is tested end to end. Select other reference-binary transports with build tags; no fork is needed for supported families. The GoBridge core and library are portable; the AWS profile binary is AWS-bound.

Reference Binary and Composition Root

The reference cmd/gobridge binary is a blank root without build tags: no transport, store or telemetry exporter is linked. File configuration, file:// credentials and the admin/monitor HTTP API remain available. A config naming a transport or store that was not compiled in fails with an unknown-kind error. To build the Kubernetes profile’s MQTT and memory/SQLite set locally, run from the repository root:

make build-gobridge GOBRIDGE_TAGS=gobridge_mqtt,gobridge_native
./cmd/gobridge/gobridge.out -version

Direct Go builds use go -C cmd/gobridge build -tags gobridge_mqtt,gobridge_native -o gobridge.out .. Use gobridge_aws for SQS and DynamoDB, gobridge_azure for Service Bus, gobridge_amqp091 or gobridge_amqp10 for AMQP, gobridge_http for the HTTP transport, and gobridge_otel for OTLP metrics and tracing. Combine tags with commas or use gobridge_all. The HTTP API itself does not require a family tag.

No family registers processors (tenant, filter, transform), selects a different config source, or adds a credential backend. Those still require a custom composition root and explicit wiring. Supported transport/store families do not: their decoder, factory and seed calls live in plugins_<family>.go. See PLUGIN.md for the complete family table and extension contract.

The reference binary takes these flags:

Flag Default Description
-version false Print version, commit SHA and sorted compiled families, then exit. Unstamped metadata is dev; a blank root reports families=[].
-config bridge.yaml Path to the configuration file
-log-level info Log level; an unrecognised value is rejected
-credentials-dir credentials Base directory backing file:// credential URIs
-start-empty true Start with an empty configuration when -config does not exist. Set false to refuse to boot a bridge that would carry no routes – a mistyped path or an unmounted volume then fails the process instead of quietly transporting nothing.
-seed-managed-subscriptions Seed the managed-subscription baseline of a persistent/exclusive MQTT session and exit (repeatable). session-id attests a new broker identity with no subscriptions; session-id=filter,filter records the exact filters the existing broker session holds. Idempotent – see durable sessions.

Starting empty is a real, supported state, but a limited one: the empty configuration defines no http block, and this composition root binds its HTTP listeners once at startup, so a process that started empty serves no admin API and no health probes. Create the configuration file to converge the routes – the watcher watches the directory, so file creation is picked up – and restart the process to bring up the HTTP listeners.

Deployment Models

GoBridge supports two deployment modes set via bridge.deployment_mode:

Within these modes, the topology field in the bootstrap config controls how replicas discover and share configuration:

Criteria single filesystem_replicated
Replicas 1 N (shared config file)
Config coordination N/A File-based poll watcher
shared_outbox routes Supported Not supported (use DynamoDB profile)
Session lease coordination Supported Not supported (use DynamoDB profile)
Best for Dev, low-throughput Scale-out without DynamoDB

Choosing a Topology

Use single when you run exactly one replica or during development. Use filesystem_replicated when you want horizontal scale-out with a shared filesystem (e.g., EFS, NFS, GlusterFS) and do not need durable outbox or lease coordination. For full high-availability with outbox and lease support, use the clustered deployment mode with DynamoDB-backed stores instead.

bridge:
  id: my-bridge
  deployment_mode: standalone   # or "clustered"
  shutdown_timeout: 45s   # process shutdown budget
  drain_timeout: 30s      # runtime drain ceiling, keep below shutdown_timeout
  # Outbox drain batch ceiling.
  per_record_drain_timeout: 3s
  max_drain_timeout: 20s

Configuration Delivery

GoBridge separates bootstrap configuration (deployment-level settings) from bridge configuration (routes, sessions, transports). The bootstrap config tells the runtime where to find the bridge config and how to resolve secrets.

Three Delivery Methods

  1. Mounted file – Write a bootstrap JSON file to the container filesystem and set GOBRIDGE_AWS_BOOTSTRAP_FILE to its path. This is the recommended approach for container orchestrators that support config volumes (ECS task definitions, Kubernetes ConfigMaps).

  2. Inline environment variable – Set GOBRIDGE_AWS_BOOTSTRAP_JSON to the full JSON content. Useful for small configs in environments where file mounts are awkward.

  3. Remote config store – Use the DynamoDB config loader or a custom ports.Loader implementation. The bridge config is fetched from a remote store at startup and watched for changes.

Bootstrap Config Fields

The bootstrap loader reads JSON (from GOBRIDGE_AWS_BOOTSTRAP_JSON or the file named by GOBRIDGE_AWS_BOOTSTRAP_FILE) — it is not YAML:

{
  "bridge_id": "my-bridge",
  "config_file_path": "/var/lib/gobridge/bridge.yaml",
  "admin_api_key_param": "/gobridge/admin-key",
  "poll_interval": "5s",
  "node_role": "control",
  "topology": "single"
}
Field Required Default Description
bridge_id Yes Unique bridge identifier
config_file_path Yes Path to the bridge YAML/JSON config file
admin_api_key_param Yes SSM parameter path for the admin API key
monitor_api_key_param No SSM parameter path for the monitor API key
poll_interval No 1s Config file poll interval
node_role No control control or worker. Also selects the admin config single-writer posture — see Admin Config Transactions and the Single-Writer Posture
topology No single single or filesystem_replicated
admin_addr No :8080 Admin server listen address
monitor_addr No :8081 Monitor server listen address
transport_http_addr No :8082 Transport HTTP server listen address

Poll vs Notify

The bridge config file watcher supports two modes configured via the config_watch section in the bridge config:

Mode Mechanism Best for
notify Hybrid: filesystem events (fsnotify) on the containing directory, debounced, plus a periodic hash-resync backstop (30s default) Local disks and Kubernetes ConfigMap volume mounts, fast change detection
poll Periodic SHA-256 content comparison NFS/EFS, network mounts, subPath mounts

notify mode is safe for Kubernetes ConfigMap volume mounts: the watcher sees the atomic ..data symlink swap, and the hash-resync backstop catches anything fsnotify misses (see Kubernetes ConfigMap Config). Keep poll mode for network filesystems (EFS, NFS) and subPath mounts, which do not deliver reliable inotify events.

config_watch:
  mode: poll
  poll_interval: 30s

Config-file writes must be atomic

Pre-deploy checklist item. Any process that writes the watched bridge config file — a deploy script, a templating tool (Helm, Jsonnet, envsubst), or a CI job — MUST write atomically: render to a temporary file in the same directory, then rename it over the target. Never truncate-and-rewrite the file in place.

The watcher can read a truncated in-place write mid-flight. A partial-but-valid document — one that parses with only bridge.id and no routes — swaps live and stops forwarding traffic while /health and /ready stay green. Validation checks bridge.id first and the graph validator permits an empty route graph (config/validate.go, validate.ValidateBlueprintGraph), so the runtime loads the empty config as valid and logs no error. rename is atomic, so the watcher only ever sees the complete old or complete new file.

GoBridge’s own writer already renames; this requirement is only for external writers. See External config writers must write atomically for the failure mode and the fix.

Admin Config Transactions and the Single-Writer Posture

The admin API supports config transactions (/api/v1/admin/config/transactions) that durably rewrite the bridge config on commit. The file-based profile’s config store is a parser.FileStore over the shared EFS volume, which is non-CAS (it has no atomic compare-and-swap SaveIfVersion). On a non-CAS store, two admin instances that both read version N could each pass the read-time version guard and clobber each other’s acknowledged commit (a silent lost update). To prevent that, the admin server fails closed: a durable commit is refused with HTTP 500 unless the process asserts it is the sole durable writer of the store (httpapi.Config.ConfigSingleWriter = true).

The bootstrap App derives that assertion from node_role:

node_role Single-writer asserted? Config-txn commit
control (and the default) Yes Permitted — this node owns the RW config store and is the only admin writer
worker No Refused (fail closed) — a worker mounts EFS read-only and is not a durable writer

This is correct for both reference topologies: GoBridgeSingle is a single task, and GoBridgeCluster forces the control service to DesiredCount=1 (workers mount EFS read-only), so exactly one node is ever the config writer.

When you need a CAS config store instead. If you build a genuine multi-writer deployment — more than one node accepting admin config transactions against the same shared backend concurrently — asserting single-writer would be unsafe. Such a deployment MUST wire a ports.ConditionalConfigStore (compare-and-swap) config store instead, which serializes concurrent commits safely regardless of the single-writer flag. The bundled parser.FileStore is not CAS today, so multi-writer config-txn commits remain refused on the file-based profile — keep to the single control writer, or supply a CAS store.

Secret Management

GoBridge resolves secrets at startup through the credential URI system. The bootstrap config references SSM Parameter Store paths; the runtime fetches the actual values before building the bridge.

Credential Flow

flowchart LR
    BC[Bootstrap Config] -->|admin_api_key_param| SSM[SSM Parameter Store]
    SSM -->|GetParameter| App[App Runtime]
    BC -->|http_receiver_api_key_params| SSM
    App -->|X-API-Key header| HTTP[HTTP API]

What Gets Resolved

  1. Admin API keyadmin_api_key_param points to an SSM SecureString read at startup to authenticate admin API requests.
  2. Monitor API key – Optional monitor_api_key_param gives the monitor API a separate key; otherwise the admin key covers both.
  3. HTTP receiver/sender API keyshttp_receiver_api_key_params and http_sender_api_key_params map receiver/sender IDs to SSM paths, resolved into the transport options before the runtime builds.
  4. Transport credentials – A credentials_uri in session/receiver/sender options resolves from SSM (pms://) or disk (file://). The file:// store tolerates read-only/immutable mounts, so a Kubernetes Secret mounted read-only does not crash-loop it – see the Kubernetes secret-mount cookbook. Rotation cadence and live-connection behavior: Credentials & HTTP API and Credential Rotation.

Bootstrap Config Example with Secrets

{
  "bridge_id": "production",
  "config_file_path": "/var/lib/gobridge/bridge.yaml",
  "admin_api_key_param": "/gobridge/prod/admin-key",
  "monitor_api_key_param": "/gobridge/prod/monitor-key",
  "http_receiver_api_key_params": {
    "http-in": "/gobridge/prod/receiver/http-in-key"
  },
  "poll_interval": "5s",
  "topology": "single"
}

Networking and Ports

GoBridge runs three independent HTTP servers on separate ports, so network policies can keep management traffic internal while exposing transport traffic.

Port Architecture

flowchart TD
    subgraph GoBridge Container
        A[":8080\nAdmin API"]
        M[":8081\nMonitor API"]
        T[":8082\nTransport HTTP"]
    end
    LB[Load Balancer] --> A
    LB --> M
    LB --> T
    Client[External Clients] --> LB
    style A fill:#36f,stroke:#333
    style M fill:#3a6,stroke:#333
    style T fill:#f96,stroke:#333

Port Reference

Port Server Auth Purpose Expose externally?
:8080 Admin X-API-Key (required) Config CRUD, health, runtime status No (internal only)
:8081 Monitor X-API-Key (optional) Metrics, Prometheus scrape, health probes Internal or monitoring VPC
:8082 Transport HTTP Per-receiver api_key Message ingress/egress Depends on use case

Network Policy Recommendations

Health, Shutdown, and Containers

Probes, the shutdown sequence and its budgets are in Health Checks and Graceful Shutdown. Image pinning, orchestrator integration and building your own image are in Container and Orchestrator Deployment.

Observability

GoBridge provides structured logging, metrics, and distributed tracing through pluggable adapters. The runtime instruments message delivery automatically – build cmd/gobridge with gobridge_otel for OTLP metrics and tracing, or wire exporters explicitly in a custom composition root.

Structured Logging

Use slog with the JSON handler for machine-parseable output. The observability.CorrelationHandler wrapper automatically injects contextual fields into every log record:

Field Source
correlation_id x-bridge.correlation-id header (auto-generated if missing)
trace_id Active span’s W3C trace-id when the tracer exposes ports.SpanIdentity (OTel); upstream traceparent fallback
span_id Active span’s W3C span-id (same capability); upstream traceparent fallback

The cross-hop log join key x-bridge.correlation-id resets per hop unless the downstream receiver’s route sets trust_bridge_headers: true (ingress strips and re-generates it otherwise).

jsonHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelInfo,
})
logger := slog.New(observability.NewCorrelationHandler(jsonHandler))

We recommend info level for production and debug for staging, set via bridge.log_level.

Metrics Export

Two built-in adapters are available:

Adapter Package Backend
CloudWatch adapters/aws/metrics/cloudwatch AWS CloudWatch
OTLP adapters/otel/metrics Any OTLP-compatible collector

The shipped AWS file-based image accepts only noop or cloudwatch for its metrics_exporter — those are the sole values its bootstrap wires (deployment/aws/lib/bootstrap/metrics.go), and any other value is rejected at startup. For OTLP, build cmd/gobridge with gobridge_otel and set OTEL_EXPORTER_OTLP_ENDPOINT or the signal-specific metrics/traces endpoint variables. It is not reachable from the stock AWS image; the Kubernetes Dockerfile accepts the tag through GO_BUILD_TAGS.

The runtime emits metrics automatically when a MetricsExporter is registered. Key metrics include DeliveryE2ELatency, MessagesReceived, MessagesSent, RouteErrors, DLQEntries, and OutboxDepth. See Scenario 18: Observability for the full metrics table and Go bootstrap code.

Distributed Tracing

The runtime creates a bridge.handleDelivery span around each message delivery with attributes for route_id, envelope_id, and (when an ingress traceparent is present) trace_id; W3C traceparent headers propagate through the bridge. Use a sampling ratio of 0.1 (10%) in production to control costs.

Alert Condition Severity
Bridge unhealthy Health check failing > 1 min Critical
High error rate RouteErrors / MessagesReceived > 5% High
DLQ growing DLQEntries sum > 100 High
Config reload failure Reload rejected Medium
Circuit breaker open State = open > 5 min Medium

Configure these alerts in your monitoring system (CloudWatch Alarms, Grafana, PagerDuty) using the metrics emitted by the bridge runtime. For consumers not using the CDK constructs, the programmatic path is cloudwatch.EnsureAlarms(ctx, client, cloudwatch.DefaultAlarms(ns, snsTopic)) alongside an exporter constructed with WithRollupMetrics(DefaultRollupMetrics()...) and the same namespace – see Monitoring. Which alarms the shipped CDK bundle provisions for each deployment shape, which ones DefaultAlarms() provisions instead, and which ones you must author yourself are listed in CloudWatch alarms — together with the rollup metrics every built-in alarm depends on to match a series at all.

Sizing, concurrency, and horizontal-scale guidance is on its own page: Scaling a GoBridge deployment.

What’s Next

Guide Description
AWS Deployment Guide ECS Fargate, EFS, SSM, CloudWatch, CDK constructs
GCP Deployment Guide Cloud Run, GCS, Secret Manager (planned)
Bare-Metal Guide Systemd, NGINX, manual TLS (planned)

For bridge configuration, see:

Document Description
Configuration Overview Lifecycle, sources, layered config
Configuration Reference Every config field documented
Transport Configuration MQTT, SQS, Azure SB, HTTP options
Processors & Stores Filter, transform, circuit breaker, store backends
Credentials & HTTP API Credential URIs and admin API endpoints
Scenarios Progressive walkthroughs from simple to clustered
Runbooks Symptom-first incident runbooks and upgrade/rollback procedures