Platform

Platform overviewArchitectureWorkflow orchestrationGitOps configurationGovernance and AAAAI and MCPKnowledge and contextRuntime and executionEvidence and monitoring

Use cases

All use casesProduction incidentRelease preparationHotfix to productionSecurity scan triage
Why NopsAIIntegrationsSecurity

Resources

All resourcesAI agent governanceMCP governanceMCP securitySelf-hosted platforms
PricingGitHub

Company

How a run worksAboutContactBook a demo

Architecture and networking

Which containers exist, how they address each other, which ports are published, and why a runner never needs an inbound port.

ConceptNew userAdministratorOperator

Key points

  • Compose creates one bridge network named nopsai-net; every service resolves the others by container name on it.
  • Five ports are published to the host, all on 127.0.0.1 by default: UI 80, API 8080, git-bot 8081, dispatcher 9091, PostgreSQL 5432.
  • In-network addresses are fixed by Compose: the API is http://nopsai:8080, AAA is http://aaa:8082, git-bot is http://nopsai-git-bot:8081, Gotenberg is http://gotenberg:3000, and the Docker socket proxy is tcp://docker-socket-proxy:2375.
  • The dispatcher process listens on :9090 inside its container; the published host port 9091 maps onto it. Callers find it through DISPATCHER_GRPC_ADDRESS.
  • Runners dial out to the dispatcher and keep that stream open. Work is assigned back over it, so a runner needs no inbound port and no public address.
  • The docker-runner service in the checked-in Compose file only builds the image — its entrypoint is true. A working runner is installed separately.

How it works

Two planes share the network. The durable control plane is the API, AAA, PostgreSQL, git-bot, Gotenberg, and the socket proxy: everything that must survive a restart lives there. The ephemeral execution plane is the dispatcher, runners, per-run agents, and step containers.

Only two hops need to work from a runner host: the dispatcher gRPC endpoint it dials, and the API URL the per-run agent calls back on for status, logs, outputs, and approvals. Everything else the runner needs arrives over those two connections.

The Docker socket proxy exists so System Logs can read container logs without handing the API a writable Docker socket. It exposes allow-listed reads only.

When the generated runner install detects that the dispatcher address resolves to the machine running the command, it rewrites the address to host.docker.internal and adds a host-gateway mapping, because a bridge container cannot reach the host through localhost.

Inbound Git traffic terminates at git-bot on 8081 for GitHub App deliveries, while other providers deliver to the API through a configured Git webhook source. Nothing else needs to be reachable from the internet.

Examples

Compose topology and published portstext
host (127.0.0.1)          nopsai-net (bridge)

:80    ───────────────▶  nopsai-ui
                              │ http://nopsai:8080
:8080  ───────────────▶  nopsai ──────┬───────▶ aaa            (http://aaa:8082)
                              │       ├───────▶ db             (postgres:5432)
                              │       ├───────▶ gotenberg      (http://gotenberg:3000)
                              │       └───────▶ docker-socket-proxy (tcp://…:2375)
                              │
:8081  ───────────────▶  nopsai-git-bot ──────▶ nopsai
                              │
:9091  ───────────────▶  dispatcher (listens :9090)
                              ▲
                              │ runner dials out and keeps the stream open
                         docker runner ──▶ agent ──▶ step containers

:5432  ───────────────▶  db
Inspect the network and what is attached to itbash
docker network inspect nopsai-net --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}\n{{end}}'
Result

One line per attached container, including any runner you install later on the same network.

Check each published surfacebash
curl -s localhost:8080/livez
curl -s localhost:8080/healthz
curl -s localhost:8080/version
curl -sI localhost/ | head -1
Result

/livez answers as soon as the process is up; /healthz turns ready once PostgreSQL is reachable; /version returns build identity; the UI answers with an HTTP status line.

Implementation evidence

  • docker-compose.yaml

    Network name, published ports, and the in-network service addresses.

  • services/dispatcher/internal/app/app.go

    Dispatcher listen address default.

  • services/nopsai/internal/runnerinstall/docker.go

    Host-gateway rewrite and runner network placement.