CLI Reference

add realtime

Scaffold the realtime notification gateway: a dedicated, horizontally scalable cmd/realtime binary.

Reach for this generator when the project needs to push live updates to connected clients: a notification badge, a live order status, a chat message.

go tool gpsystem add realtime

Adds cmd/realtime: a dedicated server that holds client streaming connections (WebSocket, with an HTTP-streaming/SSE fallback) and delivers messages published through notify/broadcast from any process. It carries no business dependencies, so it scales freely and independently of the API/worker processes; see Realtime for the architecture and scaling story.

An explicit, opt-in command, not a new project default: like add worker, you run it when the project actually needs the gateway, and it adds a Traefik-exposed service.

What it generates

cmd/realtime/main.go
func main() {
    cfg := envconf.MustLoad[config.Config]()
    ctx := context.Background()

    err := realtime.Run(ctx, cfg.Realtime, func(topics *realtime.TopicAuth) error {
        // Authorize shared topic channels here, e.g.:
        // topics.Allow("announcements", func(id *rbac.Identity) bool { return true })
        // gpsystem:topics
        return nil
    },
        // gpsystem:closers
    )
    if err != nil {
        log.Fatal(err)
    }
}
internal/platform/config/config.go
Realtime realtime.Config

realtime.Config reuses VALKEY_* (queue.Config) and JWT_* (auth.Config); no new secrets to manage. See the Realtime page for the full REALTIME_* table.

compose.yml and .env.example

Adds a realtime service (path-routed under /realtime on the project's existing APP_HOST, so no new DNS entry or CORS case): no container_name (so --scale works), a Traefik health-check label pointed at /realtime/healthz (so a draining instance is evicted from the load-balancer pool), responseForwarding.flushInterval=-1 (so streamed bytes flush immediately through the proxy).

compose.yml
realtime:
  build: { context: ., dockerfile: Dockerfile, target: development, args: { CMD: realtime } }
  depends_on:
    valkey:
      condition: service_started
  stop_grace_period: 30s
  labels:
    - traefik.http.routers.shopRealtime.rule=Host(`${APP_HOST}`) && PathPrefix(`/realtime`)
    - traefik.http.services.shopRealtime.loadbalancer.healthCheck.path=/realtime/healthz
.env.example
REALTIME_LISTEN_ADDR=:3000
REALTIME_DRAIN_TIMEOUT=20s
REALTIME_CHANNEL_PREFIX=<project>
REALTIME_MAX_CONNECTIONS=0

Scale it independently of every other service:

docker compose up -d --scale realtime=3

Authorizing shared topics

Every connection is server-side subscribed to its own user:<subject> channel automatically; no code needed for that. Shared topics are opt-in: register a callback in cmd/realtime/main.go at the // gpsystem:topics anchor, and a client subscribes to it by name. A topic with no registered Allow is never subscribable; see Realtime.

The gateway defaults to server-to-client only; if the client also needs to write (not just POST to a separate endpoint), extend the realtime.Run call with realtime.WithRPC(...) and register methods in main.go: see Realtime: Client-to-server RPC. The generator does not scaffold this: it is a manual Option added to realtime.Run's variadic arguments.

Wiring a notification to it

add realtime only wires the gateway itself. Run it before your first add notification to have the broadcast channel wired into the shared notify.Sender automatically; if notifications already exist, add notify.ChannelBroadcast to their Via and the broadcast construction by hand (see the note on add notification).

add realtime writes at the gpsystem:imports/gpsystem:config anchors in platform config and the gpsystem:services/gpsystem:env anchors in compose.yml/.env.example. If one of these anchor comments is missing, the command stops and prints the block to paste in by hand, the same way add mail does.

Copyright © 2026