The runtime container failed with ERR_MODULE_NOT_FOUND: axios when integrations/cisco-dect/client.js tried to load. Root cause is Node's ESM resolver: it walks UP from the IMPORTING file looking for node_modules, never sideways into siblings. Container filesystem before: /workspace/dect-relay-agent/node_modules/ <- axios lives here /workspace/dect-relay-agent/index.js <- ok, finds it by walking up /workspace/integrations/cisco-dect/client.js <- walks up to /, never sees axios Node 20.20 has --experimental-detect-module ON by default, so client.js is still treated as ESM (starts with `import`), and the resolver correctly reports "cannot find package 'axios'" rather than syntax-erroring on the import keyword. But it still can't find the package — the location is wrong. Fix: install node_modules at /workspace/ so BOTH the agent AND the shared modules can find it by walking up. /workspace/node_modules/ <- axios here now /workspace/package.json <- also here, "type":"module" for all descendants /workspace/dect-relay-agent/index.js <- walks up to /workspace/node_modules ✓ /workspace/integrations/cisco-dect/client.js <- walks up to /workspace/node_modules ✓ /workspace/utils/httpDigestAuth.js <- same ✓ WORKDIR moves from /workspace/dect-relay-agent to /workspace, and CMD changes accordingly: node --enable-source-maps dect-relay-agent/index.js Rebuild + reship the bundle with `./dect-relay-agent/bundle.sh` and `./install.sh` on the DC host — it's an idempotent upgrade. Verified locally (agent modules import cleanly using the same directory shape as the container).
118 lines
5.5 KiB
Docker
118 lines
5.5 KiB
Docker
# 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"]
|