Files
iot-dc3/docs/en/guide/usage.md

14 KiB

title
title
Deployment Modes and Image Registries

Deployment Modes and Image Registries

IoT DC3 runs as four Compose stacks: db brings up dependencies, dev builds from source, app pulls prebuilt images, and optional adds observability. This page covers what each stack does, the make lifecycle, which registry images come from, and which ports are exposed. By the end you'll know which stack to pick, which registry to use, and which ports to lock down in production.

If you're ready to run the platform, you're in the right place. To set up environment variables first, see Environment Variables. To develop locally from source, see Quick Start.

Four stacks, each with its own role

There's no single Compose file — the platform is split by responsibility into four stacks, one per Compose file under dc3/. They layer on top of each other: dependencies come up first, then the application, then observability as needed.

  • db (docker-compose-db.yml): the infrastructure layer, just two containers — dc3-postgres (PostgreSQL with AGE/TimescaleDB/pgvector, holding metadata, time-series values, alarms, and Agentic sessions) and dc3-rabbitmq (the message bus for data and command streams). Every other stack waits for this one.
  • dev (docker-compose-dev.yml): the source-build stack. It builds dc3-gateway and the four centers ( auth/manager/data/agentic) from local Dockerfiles. Use it when you're changing backend code and need local debugging. The frontend isn't part of this stack — start it separately with pnpm dev.
  • app (docker-compose.yml): the prebuilt-image stack. It pulls remote images and runs them as-is, including dc3-web, the gateway, the four centers, and a set of driver containers. Use it for evaluation, demos, and production — no compilation, fast startup.
  • optional (docker-compose-optional.yml): observability and optional dependencies — EMQX, Elasticsearch/Logstash/Kibana, Prometheus, Grafana, APM, and several exporters. Add it when you need it; it doesn't sit on the critical path. See Observability.

::: info Choosing between dev and app Use dev when you need to change Java code (built on the spot, rebuild after each change). Use app when you just want to run the platform for evaluation or production (pull images, fastest path). Both share the same db/optional dependency stacks. :::

Deployment topology: what faces outward, what stays internal

The diagram shows how the four stacks layer and where the ingress boundary sits. In the production form (the app stack), only dc3-web and dc3-driver-listening-virtual are published to the host. The gateway and the four centers live on the internal dc3net network, reached through the frontend reverse proxy or internal calls — never exposed directly.

flowchart TB
  subgraph Ingress["External Ingress (only these two are published to the host)"]
    Web["Frontend dc3-web<br/>(8080 / 8443)"]
    LV["Listening Driver dc3-driver-listening-virtual<br/>(TCP 6270 / UDP 6271)"]
  end

  subgraph App["app stack: prebuilt images"]
    GW["Gateway dc3-gateway<br/>(internal 8000)"]
    Auth["Auth Center dc3-center-auth"]
    Mgr["Manager Center dc3-center-manager"]
    Data["Data Center dc3-center-data"]
    Agt["Agentic Center dc3-center-agentic"]
    Drv["Driver Container Group<br/>modbus-tcp / opc-ua / mqtt / ..."]
  end

  subgraph Dev["dev stack: source build (replaces app)"]
    DevBuild["Build gateway and the four centers on the spot"]
  end

  subgraph DB["db stack: infrastructure"]
    PG[("dc3-postgres<br/>PostgreSQL + Timescale")]
    MQ["dc3-rabbitmq<br/>Message Bus"]
  end

  subgraph Opt["optional stack: observability (on demand)"]
    Obs["EMQX / ELK / Prometheus / Grafana"]
  end

  Web --> GW
  GW --> Auth & Mgr & Data & Agt
  Data --> MQ
  Drv --> MQ
  LV --> Drv
  Auth & Mgr & Data & Agt --> PG
  App -.depends on.-> DB
  Dev -.depends on.-> DB
  Opt -.layers on.-> App

::: danger Only web and listening-virtual are exposed In the app (production) stack, only dc3-web (8080/8443) and dc3-driver-listening-virtual (TCP 6270 / UDP 6271) are published to host ports. The gateway's 8000, the four centers' HTTP/gRPC ports, the database, and the message queue * all stay on the internal network* — do not expose them to the public internet.

For debugging convenience, the dev stack additionally publishes the gateway's 8000, each center's HTTP port ( 8300/8400/8500/8600), and the gRPC ports for auth/manager/data (9300/9400/9500; agentic has no gRPC server). That's a development convenience — don't carry it into production. All published ports bind to DC3_BIND_HOST=127.0.0.1 ( local only) by default; switch to 0.0.0.0 only when you need cross-host access, and narrow the port list first. :::

The make lifecycle: one set of commands for every stack

All stacks share the same make targets. Variables pick the stack, the services, and the image registry. Run every command from the iot-dc3/ directory.

Core lifecycle targets:

  • make build — build images (the dev stack compiles on the spot; the app stack usually needs no build)
  • make up — start (-d, detached)
  • make down — stop and remove containers (keeps data volumes)
  • make config — render and validate the Compose config without starting anything
  • make logs — follow logs (-f --tail=200)
  • make reset — ⚠️ down + delete data volumes, requires explicit confirmation (see the danger note below)

Variables for picking the stack and services:

Variable Default Purpose
STACK dev Pick the stack: db / dev / app / optional
SERVICES (empty = all) Operate only on the listed services, space-separated, e.g. SERVICES="gateway agentic"
GROUP (empty) Predefined service group: center (the four centers) / core (centers + gateway) / drivers (driver group)
COMPOSE podman compose Container runtime (this repo standardizes on podman)

GROUP is shorthand for SERVICES — center expands to auth manager data agentic, core adds gateway, and drivers expands to the built-in driver set. You can combine SERVICES and GROUP.

::: code-group

# dependencies → observability → source-build stack
make up STACK=db
make up STACK=optional
make up STACK=dev
make up STACK=db
make up SERVICES="gateway agentic"   # start only gateway + agentic center
make up GROUP=core                    # start the four centers + gateway
make logs SERVICES="gateway agentic"  # tail logs for just these two
make config STACK=app                 # render the config only, don't start
make down STACK=dev                   # stop the dev stack, keep data

:::

::: danger reset deletes data volumes make reset runs down and deletes data volumes — every metadata record, time-series value, and alarm in PostgreSQL is lost. It has a hard gate: it runs only when CONFIRM_RESET_VOLUMES=true is set, otherwise it refuses.

make reset STACK=db CONFIRM_RESET_VOLUMES=true

Be careful in production. Once the volume is gone, the next database startup re-runs the initdb seed scripts (see the final section). :::

Image registries: REGISTRY picks the repository, DC3_IMAGE_TAG picks the version

REGISTRY decides which repository images come from. At the make layer it resolves to DC3_IMAGE_REGISTRY — the namespace Compose actually reads:

REGISTRY Resolved DC3_IMAGE_REGISTRY Use for
auto (default) Reads DC3_IMAGE_REGISTRY from the environment/.env, falling back to pnoker if unset Custom private repository, or following .env
global pnoker (Docker Hub) Overseas / general networks
cn registry.cn-beijing.aliyuncs.com/dc3 (Aliyun) Mainland China, faster pulls

Any other value fails with Unsupported REGISTRY. The image version is controlled by DC3_IMAGE_TAG (default 2026.6) — all services and dependency images share the same tag. In production, pin a specific version rather than latest.

::: code-group

make up STACK=db REGISTRY=global
make up STACK=app REGISTRY=global
make up STACK=db REGISTRY=cn
make up STACK=app REGISTRY=cn

:::

For example, under cn the gateway image resolves to registry.cn-beijing.aliyuncs.com/dc3/dc3-gateway:2026.6; under global it's pnoker/dc3-gateway:2026.6. The collapsed command reference in the final section lists the full image set.

::: warning Makefile uses REGISTRY, Compose uses DC3_IMAGE_REGISTRY Don't confuse the two. REGISTRY=auto|global|cn is the make selector; it injects the matching DC3_IMAGE_REGISTRY namespace into Compose. When you run podman compose directly, set DC3_IMAGE_REGISTRY yourself. :::

After the stack is up: seed data and external verification

Once the app/dev stack is running, the platform is ready. The simplest way to check the external ingress is through the frontend dc3-web (default http://127.0.0.1:8080). To hit the API from the command line you go through the gateway — but the gateway isn't exposed in the app stack, so this is usually done under the dev stack (which publishes 8000). Login is two steps: first POST /api/v3/auth/token/salt to get the salt, then POST /api/v3/auth/token/generate to trade it for a token valid for 12 hours. After that, requests carry the three auth headers X-Auth-Tenant / X-Auth-Login / X-Auth-Token.

# Only available under the dev stack where the gateway is exposed; all values are examples, replace with your own tenant/account
# 1) Get the salt (public)
curl -s -X POST http://127.0.0.1:8000/api/v3/auth/token/salt \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3"}'
# → returns a salt string valid for 5 minutes (example)

# 2) Hash the password with the salt to exchange for a token (public), returning an access token valid for 12 hours
curl -s -X POST http://127.0.0.1:8000/api/v3/auth/token/generate \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3","salt":"<from previous step>","password":"<hashed password>"}'

When the database starts for the first time on an empty data volume, the dc3-postgres entrypoint runs the 7 seed scripts under initdb in filename order, building the schema and base data in one pass:

Order Script Contents
00 00-iot-dc3-extensions.sql Enable extensions
01 01-iot-dc3-common.sql Common tables
02 02-iot-dc3-auth.sql Menus, resources, users, roles, OAuth/MCP
03 03-iot-dc3-data.sql Runtime data: alarms, notifications, rules
04 04-iot-dc3-manager.sql Entity management: devices, drivers, points, profiles
05 05-iot-dc3-history.sql Time-series hypertables
06 06-iot-dc3-agentic.sql Sessions, messages, attachments

::: warning Seed scripts run once, on an empty database These scripts run exactly once — when the data volume is empty. They won't re-run once the volume has data, and editing the SQL won't take effect on its own. To re-seed, first clear the volume with make reset ... CONFIRM_RESET_VOLUMES=true (which loses data). :::

::: danger Production secrets must be random The DC3_SECURITY_KEY and AUTH_HMAC_SECRET in .env.example, along with POSTGRES_PASSWORD / RABBITMQ_PASSWORD ( default dc3dc3dc3), are publicly known weak defaults — local use only. Replace them with strong random values before any production deployment.

AUTH_HMAC_SECRET has fail-fast protection: when the Spring profile is pre or pro and the secret is empty or still equals the default io.github.pnoker.dc3, the service throws IllegalStateException at startup and refuses to come up. See Environment Variables for what each secret does. :::

Full command and image reference

The collapsible section below is the full original text of dc3/doc/USAGE.md — every make shortcut and the image coordinates for each service across the Docker Hub and Aliyun repositories. Keep it handy while operating.

::: details Expand the full command and image list

:::

Further reading

  • Environment Variables — the default value, scope, and production value of every DC3_* / runtime variable
  • Observability — wiring up EMQX/ELK/Prometheus/Grafana in the optional stack, and what to watch
  • Local Development from Source — the local workflow using the dev stack + IDE for the backend and pnpm dev for the frontend