# syntax=docker/dockerfile:1.6 # # DECT relay agent — production image. # # BUILD CONTEXT: the REPO ROOT (not this folder). The agent imports # `../integrations/cisco-dect/*` and `../utils/httpDigestAuth.js`, so # we mirror the repo's layout under /workspace/ inside the image and # the relative paths just work. # # BUILD FROM REPO ROOT: # docker build \ # --platform=linux/amd64 \ # -f dect-relay-agent/Dockerfile \ # -t collabsupport/dect-relay-agent:0.1.0 \ # . # # Or use bundle.sh which wraps this + `docker save` + zip. # # WHY NO `apk add`: corporate DCs commonly TLS-intercept HTTPS. Alpine's # apk fetch of dl-cdn.alpinelinux.org fails inside the container when # the CA chain includes a proxy cert the container doesn't trust. We # avoid the problem entirely by not fetching anything from Alpine at # build time. Signal handling (SIGTERM / SIGINT / SIGUSR2) is done in # index.js so we don't need tini/dumb-init. # # WHY NO RUNTIME `npm install`: the bundle.sh workflow builds this # image ONCE outside the DC (where npm registry access works), saves # it as a tarball, and ships the tarball. The DC only runs # `docker load` + `docker compose up -d` — zero network calls beyond # the initial docker load. # # WHERE node_modules LIVES (subtle but critical): # /workspace/node_modules ← NOT under dect-relay-agent/ # # The agent imports `../integrations/cisco-dect/client.js`, which # in turn does `import axios from 'axios'`. Node's ESM resolver # walks UP from the IMPORTING file (client.js) looking for # node_modules — it does NOT search siblings. So if node_modules # lived at /workspace/dect-relay-agent/node_modules, then # client.js (at /workspace/integrations/cisco-dect/client.js) would # never find axios and blow up with ERR_MODULE_NOT_FOUND at runtime. # Placing node_modules one level higher fixes it: both the agent # AND the shared integrations resolve axios via /workspace/node_modules. # The package.json at /workspace/ also declares "type":"module" so # every .js file under /workspace/ is treated as ESM without needing # its own package.json. # ─── Stage 1: builder ──────────────────────────────────────────────── # Installs prod deps in a full node image (has python/build-essentials # just in case a native module needs building — currently `ws` ships # pre-built optional deps for common arches but we keep the option # open for future deps). FROM node:20-alpine AS builder WORKDIR /workspace # Copy just the package manifest first so this layer caches across # code-only changes. The agent's package.json IS the workspace # package.json — same "type":"module", same deps (ws / axios / # dotenv), just placed one directory higher. COPY dect-relay-agent/package.json ./package.json # Install only production deps. --ignore-scripts because we don't run # arbitrary postinstall from transitive deps in the container build; # any needed build steps are pinned in this Dockerfile. RUN npm install --omit=dev --ignore-scripts \ && npm cache clean --force # ─── Stage 2: runtime ──────────────────────────────────────────────── # Same base as builder, but only the artifacts we actually need at # run time (node_modules + agent source + shared integrations + utils). FROM node:20-alpine AS runtime # node:20-alpine ships a `node` user (uid 1000) that we can just use — # no need to install anything extra. Running as a non-root user is a # baseline hardening we get essentially for free. USER node # WORKDIR is the workspace root so `node dect-relay-agent/index.js` # resolves correctly AND node_modules at /workspace/node_modules is # discoverable by both the agent and the shared modules. WORKDIR /workspace # Shared node_modules (see the header comment for why it's here and # not under dect-relay-agent/). Ownership goes to `node` so the # process can read them without needing root. COPY --from=builder --chown=node:node /workspace/node_modules ./node_modules # Package manifest at workspace root — Node uses this to determine # "type":"module" for every .js file under /workspace/**. COPY --chown=node:node dect-relay-agent/package.json ./package.json # Agent source. COPY --chown=node:node dect-relay-agent/index.js ./dect-relay-agent/index.js # Shared modules the agent imports from the parent workspace. COPY --chown=node:node integrations/cisco-dect ./integrations/cisco-dect COPY --chown=node:node utils/httpDigestAuth.js ./utils/httpDigestAuth.js # Optional metadata that shows up in `docker inspect` output — useful # in the DC for "which build am I running?" without needing to poke # inside the container. ARG AGENT_VERSION=dev ARG BUILD_DATE ARG GIT_COMMIT LABEL org.opencontainers.image.title="dect-relay-agent" \ org.opencontainers.image.description="Data-center-resident WSS bridge from CollabSupport bot (cloud) to Cisco DBS-210 DECT base stations on 10.x/8" \ org.opencontainers.image.version="${AGENT_VERSION}" \ org.opencontainers.image.created="${BUILD_DATE}" \ org.opencontainers.image.revision="${GIT_COMMIT}" \ org.opencontainers.image.source="https://git.joesjavajoint.com/jmcqueen/collabSupport" # Node handles SIGTERM natively when the process installs handlers # (which we do in index.js). --enable-source-maps improves stack # traces if something crashes at runtime — cheap and always-on. CMD ["node", "--enable-source-maps", "dect-relay-agent/index.js"]