From 766a8ff5b6cb9cff4c34dfc287df6be0f0ab9b05 Mon Sep 17 00:00:00 2001 From: Jordan Simonovski Date: Mon, 13 Jul 2026 15:07:00 +1000 Subject: [PATCH 1/3] fix(docker): copy css.d.ts into app build stage to fix nightly build The TypeScript 6 upgrade (#2617) added ambient 'declare module *.css' declarations in packages/app/css.d.ts to satisfy TS2882 for side-effect stylesheet imports in pages/_app.tsx. Both Dockerfiles copied mdx.d.ts but not css.d.ts, so next build failed type-checking inside the container, breaking the App, All-in-One, and Local nightly image builds. Also drops a stray 'export' on getAlertWindowStart (only used in-file) so the pre-commit knip check passes. --- .changeset/fix-nightly-css-dts-docker.md | 9 +++++++++ docker/hyperdx/Dockerfile | 2 +- packages/api/src/tasks/checkAlerts/index.ts | 2 +- packages/app/Dockerfile | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-nightly-css-dts-docker.md diff --git a/.changeset/fix-nightly-css-dts-docker.md b/.changeset/fix-nightly-css-dts-docker.md new file mode 100644 index 0000000000..a1eb0d8763 --- /dev/null +++ b/.changeset/fix-nightly-css-dts-docker.md @@ -0,0 +1,9 @@ +--- +'@hyperdx/app': patch +--- + +Copy `css.d.ts` into the Docker build stage so the app image compiles. The +TypeScript 6 upgrade added ambient `declare module '*.css'` declarations in +`css.d.ts` to satisfy TS2882 for side-effect stylesheet imports, but the +Dockerfiles only copied `mdx.d.ts`, so `next build` failed inside the +container. diff --git a/docker/hyperdx/Dockerfile b/docker/hyperdx/Dockerfile index fdf8bd696e..bb1e2bb1df 100644 --- a/docker/hyperdx/Dockerfile +++ b/docker/hyperdx/Dockerfile @@ -52,7 +52,7 @@ COPY .yarnrc.yml yarn.lock package.json nx.json .prettierrc .prettierignore ./ts # full source is copied in the build stages that need it. COPY ./packages/common-utils/package.json ./packages/common-utils/package.json COPY ./packages/api/jest.config.js ./packages/api/tsconfig.json ./packages/api/tsconfig.build.json ./packages/api/package.json ./packages/api/ -COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/eslint.config.mjs ./packages/app/ +COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/css.d.ts ./packages/app/eslint.config.mjs ./packages/app/ # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat diff --git a/packages/api/src/tasks/checkAlerts/index.ts b/packages/api/src/tasks/checkAlerts/index.ts index 82f15525df..96606dcaa8 100644 --- a/packages/api/src/tasks/checkAlerts/index.ts +++ b/packages/api/src/tasks/checkAlerts/index.ts @@ -330,7 +330,7 @@ export const getScheduledWindowStart = ( * that history fetched up-front (see getConsecutiveWindowHistories) lines up * exactly with the window processAlert evaluates. */ -export const getAlertWindowStart = (alert: IAlert, now: Date): Date => { +const getAlertWindowStart = (alert: IAlert, now: Date): Date => { const windowSizeInMins = ms(alert.interval) / 60000; const scheduleStartAt = normalizeScheduleStartAt({ alertId: alert.id, diff --git a/packages/app/Dockerfile b/packages/app/Dockerfile index 56edc5f284..eafc9c923f 100644 --- a/packages/app/Dockerfile +++ b/packages/app/Dockerfile @@ -9,7 +9,7 @@ COPY .yarnrc.yml yarn.lock package.json nx.json .prettierrc .prettierignore ./ts # Only copy package.json for workspace resolution during yarn install; # full source is copied in the build stages that need it. COPY ./packages/common-utils/package.json ./packages/common-utils/package.json -COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/eslint.config.mjs ./packages/app/ +COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/css.d.ts ./packages/app/eslint.config.mjs ./packages/app/ RUN --mount=type=cache,target=/root/.yarn/berry/cache,id=yarn-cache \ yarn install --mode=skip-build && yarn cache clean From 223d23ba45e63a1e0750806a8b3c087e41463e01 Mon Sep 17 00:00:00 2001 From: Jordan Simonovski Date: Mon, 13 Jul 2026 15:14:15 +1000 Subject: [PATCH 2/3] ci: build release Docker images on PRs (conditional, no push) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A source-level type check can't catch failures that only surface inside the Docker image — like the css.d.ts declaration file that existed in the repo but was never COPYed into the build stage, which broke every nightly image build. Add a Docker Build workflow that builds each released image with push:false so these are caught in PR review. Builds are conditional on changed paths (via tj-actions/changed-files, matching the existing otel-collector jobs in main.yml) so unrelated PRs skip them: - OTel Collector image: docker/otel-collector or packages/otel-collector - App/prod image: api/app/common-utils/docker-hyperdx or root deps - All-in-one image: the above plus clickhouse/otel bundled-service inputs Single-arch (amd64) and warmed from the nightly gha cache scopes to keep runtimes low; build errors are arch-independent. --- .github/workflows/docker-build.yml | 147 +++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 .github/workflows/docker-build.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000000..4d1d7fc7b7 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,147 @@ +name: Docker Build +# Verifies the release Docker images actually build on PRs, without pushing. +# A source-level type check (`make ci-lint`) cannot catch build failures that +# only surface inside the image — e.g. a declaration file that exists in the +# repo but was never COPYed into the Docker build stage. Each image is built +# only when files that affect it change, so unrelated PRs stay fast. +on: + pull_request: + branches: [main] + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + # OTel Collector image (Go build via OCB) — docker/otel-collector/Dockerfile. + otel-collector-image: + name: Build OTel Collector Image + runs-on: ubuntu-24.04 + timeout-minutes: 25 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v47.0.6 + with: + files: | + docker/otel-collector/** + packages/otel-collector/** + - name: Setup Docker Buildx + if: steps.changed-files.outputs.any_changed == 'true' + uses: docker/setup-buildx-action@v3 + - name: Build (no push) + if: steps.changed-files.outputs.any_changed == 'true' + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/otel-collector/Dockerfile + target: prod + platforms: linux/amd64 + push: false + cache-from: | + type=gha,scope=otel-collector-nightly-amd64 + type=gha,scope=docker-build-pr-otel-collector + cache-to: type=gha,mode=max,scope=docker-build-pr-otel-collector + + # App/prod image (API + App + common-utils Node build) — docker/hyperdx target prod. + # This is the image the nightly css.d.ts failure surfaced in; the same builder + # stage backs the all-in-one targets below. + app-image: + name: Build App Image + runs-on: ubuntu-24.04 + timeout-minutes: 25 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v47.0.6 + with: + files: | + packages/api/** + packages/app/** + packages/common-utils/** + docker/hyperdx/** + package.json + yarn.lock + .yarn/** + tsconfig.base.json + nx.json + - name: Setup Docker Buildx + if: steps.changed-files.outputs.any_changed == 'true' + uses: docker/setup-buildx-action@v3 + - name: Build (no push) + if: steps.changed-files.outputs.any_changed == 'true' + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/hyperdx/Dockerfile + target: prod + platforms: linux/amd64 + push: false + build-contexts: | + hyperdx=./docker/hyperdx + api=./packages/api + app=./packages/app + build-args: | + CODE_VERSION=pr-${{ github.event.pull_request.number }} + cache-from: | + type=gha,scope=app-nightly-amd64 + type=gha,scope=docker-build-pr-app + cache-to: type=gha,mode=max,scope=docker-build-pr-app + + # All-in-one image (adds ClickHouse, MongoDB, OTel Collector to the app build) + # — docker/hyperdx target all-in-one-auth. Building the auth target also + # exercises the shared all-in-one-base, so no-auth need not be built + # separately. Gated on the app inputs plus the bundled-service inputs. + all-in-one-image: + name: Build All-in-One Image + runs-on: ubuntu-24.04 + timeout-minutes: 40 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v47.0.6 + with: + files: | + packages/api/** + packages/app/** + packages/common-utils/** + packages/otel-collector/** + docker/hyperdx/** + docker/clickhouse/** + docker/otel-collector/** + package.json + yarn.lock + .yarn/** + tsconfig.base.json + nx.json + - name: Setup Docker Buildx + if: steps.changed-files.outputs.any_changed == 'true' + uses: docker/setup-buildx-action@v3 + - name: Build (no push) + if: steps.changed-files.outputs.any_changed == 'true' + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/hyperdx/Dockerfile + target: all-in-one-auth + platforms: linux/amd64 + push: false + build-contexts: | + clickhouse=./docker/clickhouse + otel-collector=./docker/otel-collector + hyperdx=./docker/hyperdx + api=./packages/api + app=./packages/app + build-args: | + CODE_VERSION=pr-${{ github.event.pull_request.number }} + cache-from: | + type=gha,scope=all-in-one-nightly-amd64 + type=gha,scope=docker-build-pr-all-in-one + cache-to: type=gha,mode=max,scope=docker-build-pr-all-in-one From 44631818d5178f11108bb187faf8547bee4c2c14 Mon Sep 17 00:00:00 2001 From: Jordan Simonovski Date: Mon, 13 Jul 2026 16:14:06 +1000 Subject: [PATCH 3/3] ci: stop rebuilding the app inside the all-in-one docker check The all-in-one target does COPY --from=prod /app /app, so it already builds the full App/prod image. With the app paths in both filters, every app change ran both jobs and built the Node app twice (~8 wasted minutes). Gate the all-in-one job only on the incremental bundling inputs (ClickHouse / OTel / the shared Dockerfile); api/app/common-utils/deps are covered by the App Image job and can't affect the bundling layers. --- .github/workflows/docker-build.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 4d1d7fc7b7..3a4ff8e591 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -96,7 +96,14 @@ jobs: # All-in-one image (adds ClickHouse, MongoDB, OTel Collector to the app build) # — docker/hyperdx target all-in-one-auth. Building the auth target also # exercises the shared all-in-one-base, so no-auth need not be built - # separately. Gated on the app inputs plus the bundled-service inputs. + # separately. + # + # This target's base does `COPY --from=prod /app /app`, so it already + # rebuilds the entire App/prod image. To avoid duplicating that ~8-minute + # Node build on every app change, this job is gated ONLY on the incremental + # bundling inputs (ClickHouse / OTel / the shared Dockerfile) — NOT on + # api/app/common-utils/deps. Those are covered by the App Image job above, + # and they can't break the bundling layers, which only COPY the prod output. all-in-one-image: name: Build All-in-One Image runs-on: ubuntu-24.04 @@ -109,18 +116,10 @@ jobs: uses: tj-actions/changed-files@v47.0.6 with: files: | - packages/api/** - packages/app/** - packages/common-utils/** packages/otel-collector/** docker/hyperdx/** docker/clickhouse/** docker/otel-collector/** - package.json - yarn.lock - .yarn/** - tsconfig.base.json - nx.json - name: Setup Docker Buildx if: steps.changed-files.outputs.any_changed == 'true' uses: docker/setup-buildx-action@v3