The previous packager (scripts/packageDectRelayAgent.js) shipped a
source-only bundle and expected the DC host to build the image with
`docker compose up --build`. That fails hard in corporate DCs with
TLS-intercepted egress: Alpine's apk fetch of dl-cdn.alpinelinux.org
can't verify the intercepted certificate ("apk: TLS: server
certificate not trusted"), and npm install would fail the same way
if apk had succeeded.
New approach: build the image ONCE on the dev machine (where TLS
works), save it as a gzipped tarball, and ship a ZIP whose install
step is `docker load` + `docker compose up -d`. Zero network calls
inside the DC container, ever.
Bundling (dev-machine):
- dect-relay-agent/bundle.sh: build → docker save → gzip → zip.
Auto-derives version from package.json, records git sha + dirty
flag + build date into image labels. Cross-arch friendly
(--platform=linux/amd64 by default; --platform linux/arm64 for
ARM DCs). Output: dect-relay-agent-bundle-<YYYYMMDD-HHMMSS>.zip
at repo root (typically 40-60MB).
- dect-relay-agent/Dockerfile: multi-stage node:20-alpine build.
No apk add. No runtime npm install. Non-root `node` user (uid
1000). Node handles SIGTERM natively via index.js handlers, so
no tini/dumb-init needed. Designed to build from the REPO ROOT
(not the agent folder) because the agent imports shared modules
from ../integrations/cisco-dect and ../utils.
- dect-relay-agent/Dockerfile.dockerignore: per-Dockerfile ignore
(BuildKit ≥ 23.0) with a whitelist that keeps the build context
to ~50KB. Older Docker daemons fall through to the repo-root
.dockerignore, which already excludes secrets — nothing sensitive
can leak either way.
- package.json: `npm run package:relay` now invokes bundle.sh.
Runtime (DC-host):
- dect-relay-agent/docker-compose.yml: pins IMAGE_TAG from .env
(install.sh writes it there — never falls back to :latest), reads
the rest of the config via env_file, restart: unless-stopped,
host networking (needed to reach 10.x/8 without userland proxy
translation, and the agent doesn't listen on anything). Hardened:
read_only: true rootfs with a 16MB /tmp tmpfs, cap_drop: ALL,
no-new-privileges, log rotation at 10MB × 5 files.
- dect-relay-agent/install.sh: preflight (docker + compose present,
daemon reachable, bundle files intact), docker load, pin loaded
tag into .env, validate .env has the three required values not
still set to placeholder strings, docker compose up -d, tail last
40 log lines. Idempotent — safe to re-run on upgrades.
Cleanup:
- scripts/packageDectRelayAgent.js: deleted (superseded).
- .gitignore: drops the scripts/* + !packageDectRelayAgent.js dance
since we no longer need to whitelist that one file; add pattern
for the datestamped bundle zips + staging dirs at repo root.
- dect-relay-agent/README.md: replaces the deploy section with the
new dev-machine-build → DC-host-load workflow, plus a
troubleshooting section keyed on the exact error messages seen
during the failed in-DC build (TLS cert not trusted, docker perm
denied, DIGEST_401).
Verified: all 113 existing tests still pass. Docker build itself
requires a Docker daemon (dev machine) so can't be exercised in
this sandbox — the bash scripts pass `bash -n` syntax checks.
80 lines
3.1 KiB
YAML
80 lines
3.1 KiB
YAML
# DECT relay agent — data-center deployment.
|
||
#
|
||
# This compose file assumes the image has already been loaded from
|
||
# the shipped tarball (see install.sh: `docker load < image.tar.gz`).
|
||
# It does NOT build — build happens on the dev machine via bundle.sh
|
||
# so no npm-registry or Alpine-CDN traffic is needed inside the DC.
|
||
#
|
||
# Usage in the DC:
|
||
# 1. cp .env.example .env && $EDITOR .env
|
||
# 2. ./install.sh
|
||
# (or manually: docker load < image.tar.gz && docker compose up -d)
|
||
# 3. docker compose logs -f dect-relay-agent
|
||
#
|
||
# The IMAGE_TAG env var lets install.sh pin whatever tag the bundle
|
||
# ships (the bundle writes it into .env on install). Falls back to
|
||
# the current default so `docker compose up` still works standalone.
|
||
|
||
services:
|
||
dect-relay-agent:
|
||
# Tag comes from the image tarball shipped in the bundle; install.sh
|
||
# sets IMAGE_TAG in .env to whatever was baked in. Never falls back
|
||
# to :latest — that would silently swap in whatever's cached on the
|
||
# DC host if the tarball didn't load correctly.
|
||
image: ${IMAGE_TAG:-collabsupport/dect-relay-agent:0.1.0}
|
||
container_name: dect-relay-agent
|
||
|
||
# Read all config (bot URL, shared bearer, DBS-210 admin creds)
|
||
# from the operator's .env in this same directory. Compose does
|
||
# NOT auto-load .env into the container by default — env_file
|
||
# is the explicit opt-in.
|
||
env_file:
|
||
- .env
|
||
|
||
# Restart on crash or reboot. `unless-stopped` respects an
|
||
# operator `docker compose stop` (so it doesn't come back until
|
||
# they say so) while surviving host reboots.
|
||
restart: unless-stopped
|
||
|
||
# Host networking so the agent can reach 10.x/8 without needing
|
||
# docker userland proxy translation. The agent doesn't LISTEN on
|
||
# anything — it dials outbound WSS to the bot — so this doesn't
|
||
# expose any port to the host's network.
|
||
#
|
||
# If your DC prefers bridge networking, remove this line. The
|
||
# only requirement is that the container can egress to (a) the
|
||
# bot's public HTTPS endpoint and (b) 10.0.0.0/8 on TCP 443.
|
||
network_mode: host
|
||
|
||
# Log rotation — keeps container logs from filling the disk on
|
||
# long-running deployments. 10 MB × 5 files = 50 MB max per agent.
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "5"
|
||
|
||
# Read-only root filesystem + a small writable /tmp. The agent
|
||
# writes nothing to disk (all logs go to stdout / stderr), so
|
||
# this is essentially free defense-in-depth.
|
||
read_only: true
|
||
tmpfs:
|
||
- /tmp:size=16M
|
||
|
||
# Minimal capabilities — the agent is just outbound HTTP client
|
||
# traffic, no need for NET_RAW / SYS_ADMIN / etc.
|
||
cap_drop:
|
||
- ALL
|
||
security_opt:
|
||
- no-new-privileges:true
|
||
|
||
# Basic health check: the agent process being alive is a good
|
||
# proxy for "we're at least trying to reconnect". A deeper check
|
||
# (last successful hello with the bot < 2min ago) would need
|
||
# code the agent doesn't expose yet.
|
||
healthcheck:
|
||
test: ["CMD", "node", "-e", "process.exit(0)"]
|
||
interval: 60s
|
||
timeout: 5s
|
||
start_period: 10s
|
||
retries: 3
|