CLI Reference

add realtime

Scaffold the realtime notification gateway: a dedicated, horizontally scalable cmd/realtime binary.
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.

Explicit, retrofit-style command, not a new project default: like add worker, it is opt-in and 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 REDIS_* (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:
    redis:
      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.

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).

Older projects

Like add worker, 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; on a project predating those files' anchors, it prints the block to paste instead of failing.

Copyright © 2026