Documentation

Documentation

Build and run

A service runs in three ways: in dev mode with restart on file changes, as built JavaScript from dist/, and in a Docker container on the platform's base image.

Running locally#

You need access to the Redis of your installation (directly or through a tunnel) and a .env with its address:

npm install
npm run start:dev

start:dev runs ts-node-dev with tsconfig-paths and source-map-support, and pipes the output through pino-pretty. When files in src/ change, the process restarts. LOG_LEVEL=debug enables debug records, including those about channel subscriptions:

INFO  RedisClient: Starting redis client. DSN: redis://127.0.0.1:6379/0
INFO  RPCAdapterRedis: subscribed channel=kafkastream
INFO  RPCAdapterRedis: subscribed channel=broadcast
INFO  RPCAgnostic: started
INFO  AppServer: Setup completed

Building and running the built code:

npm run build        # rimraf dist && tsc
npm run start:prod   # node dist/index with tsconfig-paths and source maps

In start:prod, the TS_NODE_BASEURL=./dist variable switches the @app/* alias to the built files.

Base image#

All TypeScript services are built from the rockstat/band-base-ts image. It is built from the rockme-framework repository: node:22-alpine with tools for native modules (python3, make, gcc), the framework built in /usr/src/rockme and published via npm link. Thanks to this, in the service's Dockerfile a plain npm link @rockstat/rock-me-ts is enough, and the framework is taken from the image rather than from the registry.

Image tags: ng — the current development branch, latest — the stable one. It is rebuilt from rockmets with the commands make build (or make build_macos_arm64 on Apple Silicon, which builds for linux/amd64), make tag-ng, make push-ng; all together — make all-ng.

The service's Dockerfile#

ARG BASE_CONTAINER=rockstat/band-base-ts:ng
FROM $BASE_CONTAINER

LABEL band.service.version="1.0.0"
LABEL band.service.title="My service"
LABEL band.service.def_position="3x1"

WORKDIR /app

COPY package.json .
COPY package-lock.json .
RUN npm link @rockstat/rock-me-ts
RUN npm install && npm cache clean --force

COPY . .
RUN npm run build

ENV REDIS_DSN=redis://redis:6379
ENV NODE_ENV=production

CMD [ "npm", "run", "start:prod" ]

Layer order matters: first only the manifests are copied and the dependencies are installed, then the sources. This way editing the code does not invalidate the npm install cache. .dockerignore must exclude node_modules, dist, and .env*.

The band.service.* labels are read by the Dashboard: the version, a human-readable name, and the position of the service's card on the dashboard.

If the framework is listed in package.json as file:../rockmets, that path does not exist in the image. kafkastream solves this by copying: RUN cp -r /usr/src/rockme /app/rockmets after npm i. It is simpler to specify a regular package version and rely on the npm link from the base image.

Publishing the image#

Every service has the same makefile:

build:
	docker build -t my-service .

build_amd64:
	docker buildx build --platform linux/amd64 -t my-service .

tag-ng:
	docker tag my-service rockstat/my-service:ng

push-ng:
	docker push rockstat/my-service:ng

all-ng: build_amd64 tag-ng push-ng

push-dev:
	docker tag my-service rockstat/my-service:dev
	docker push rockstat/my-service:dev

The working cycle: make all-ng builds the image for linux/amd64 and publishes it with the ng tag, make push-dev ships a test build under the dev tag without touching ng.

Versions#

The version is stored in three places: package.json, the band.service.version label in the Dockerfile, and .bumpversion.cfg. bumpversion bumps all three at once:

make bump-patch   # 1.0.0 -> 1.0.1
make bump-minor   # 1.0.1 -> 1.1.0

The configuration picks up the version automatically via <%= pkg.version %>, so it is visible in the logs and in the service's status.

Container environment variables#

VariableRequiredValue
REDIS_DSNyesredis://redis:6379 inside the platform network
NODE_ENVyesproduction
LOG_LEVELnoinfo by default, warn for quiet services
STATSD_HOSTnothe Telegraf/StatsD host; without it, metrics are not sent
TZnoUTC, already set in the base image
SERVICE_GROUP, WORKER_IDfor enrichersthe group and the worker number
customas neededKAFKA_BROKERS, CH_DSN, and others that the configuration reads

Checklist before the first run#

  • rpc.name is unique in the installation, and for multiple instances it includes WORKER_ID.
  • All external connections are made in setup() before registering bus methods.
  • The broadcast or enrich handler does not wait on the network and returns control immediately.
  • Secrets come from the environment; in YAML there is only <%= env.NAME %>.
  • The Dockerfile inherits from rockstat/band-base-ts:ng, and the band.service.* labels are filled in.
  • make all-ng passes on a clean machine, and the image starts with just a REDIS_DSN.