Skip to content

chore(deps)(deps): bump @opentelemetry/sdk-node from 0.220.0 to 0.221.0 in /apps/api #533

chore(deps)(deps): bump @opentelemetry/sdk-node from 0.220.0 to 0.221.0 in /apps/api

chore(deps)(deps): bump @opentelemetry/sdk-node from 0.220.0 to 0.221.0 in /apps/api #533

name: validate-compose
# Validates every docker-compose YAML and every meaningful overlay
# combination — without actually building images or booting services.
# Catches syntax errors, missing env vars, malformed Traefik labels, and
# overlay-merge regressions before they hit a developer's `up`.
on:
push:
branches: [main]
paths:
- "infra/compose/**"
# Root gate scripts are validated here (shellcheck job) — keep the
# trigger in lockstep so direct pushes touching them still run it.
- "scripts/**"
- "setup.sh"
# The yamllint job lints every workflow file, so any workflow edit
# must trigger this run — not just edits to this file.
- ".github/workflows/**"
# Prod Dockerfiles COPY the whole app context, so any app source
# change can break the prod image build — not just Dockerfile or
# lockfile edits.
- "apps/api/**"
- "apps/ui/**"
pull_request:
branches: [main]
concurrency:
group: infra-compose-validate-compose-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
compose-config:
name: docker compose config (all overlay combinations)
runs-on: ubuntu-24.04
timeout-minutes: 5
# Build contexts point at monorepo apps (`../../../apps/api` and
# `../../../apps/ui` from infra/compose/compose).
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Detect relevant changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
filters: |
code:
- 'infra/compose/**'
- 'scripts/**'
- '.github/workflows/**'
- name: Seed .env
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
cp .env.example .env
# Required for STACK=prod + WITH_GLITCHTIP combinations:
echo "GLITCHTIP_SECRET_KEY=$(openssl rand -base64 50 | tr -d '\n')" >> .env
echo "GLITCHTIP_PUBLIC_HOST=glitchtip.example.com" >> .env
echo "GLITCHTIP_BASIC_AUTH_USERS=user:placeholder" >> .env
echo "GLITCHTIP_SUPERUSER_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=')" >> .env
echo "GRAFANA_ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=')" >> .env
- name: Validate — dev (base)
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.development-labels.yml \
--profile dev config --quiet
- name: Validate — prod (base)
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.production-labels.yml \
--profile prod config --quiet
# Guardrail: every long-running prod-profile service in the base
# compose file must define a healthcheck so `up --wait` and
# service_healthy dependents have a readiness signal. One-shot jobs
# (restart: "no", e.g. api-migrate-prod) exit by design and are
# exempt; overlays ship sidecars and are not scanned.
- name: Guardrail — prod services define healthchecks
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh healthchecks
working-directory: infra/compose/compose
- name: Guardrail — compose images digest-pinned, no floating latest
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh digest-pins
working-directory: infra/compose/compose
- name: Guardrail — no hardcoded credential fallbacks in compose
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh credential-fallbacks
working-directory: infra/compose/compose
- name: Guardrail — valkey enforces auth and prod requires the password
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh valkey-auth
working-directory: infra/compose/compose
- name: Guardrail — rooted prod services must drop capabilities
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh rooted-caps
working-directory: infra/compose/compose
- name: Guardrail — every service blocks privilege escalation
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh no-new-privileges
working-directory: infra/compose/compose
- name: Guardrail — long-running services reserve resources (no starvation)
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh resource-reservations
working-directory: infra/compose/compose
- name: Guardrail — prod rejects unpinned image tags (dev.sh fail-closed)
if: steps.filter.outputs.code == 'true'
# Single-source: the same script runs in the local pre-push gate,
# so CI and local guardrails cannot drift.
run: ../scripts/validate-guardrails.sh prod-image-tags
working-directory: infra/compose/compose
- name: Validate — dev + observability
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.development-labels.yml \
-f docker-compose.observability.yml \
--profile dev --profile observability config --quiet
- name: Validate — dev + glitchtip
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.development-labels.yml \
-f docker-compose.glitchtip.yml \
--profile dev --profile glitchtip-dev config --quiet
- name: Validate — prod + glitchtip (+ prod labels)
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.production-labels.yml \
-f docker-compose.glitchtip.yml \
-f docker-compose.glitchtip-prod-labels.yml \
--profile prod --profile glitchtip-prod config --quiet
- name: Validate — dev + bullmq
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.development-labels.yml \
-f docker-compose.bullmq.yml \
--profile dev --profile bullmq config --quiet
- name: Validate — dev + wud
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.development-labels.yml \
-f docker-compose.wud.yml \
--profile dev --profile wud config --quiet
- name: Validate — kitchen-sink (dev + all overlays)
if: steps.filter.outputs.code == 'true'
working-directory: infra/compose/compose
run: |
docker compose \
-f docker-compose.yml \
-f docker-compose.development-labels.yml \
-f docker-compose.observability.yml \
-f docker-compose.glitchtip.yml \
-f docker-compose.bullmq.yml \
-f docker-compose.wud.yml \
--profile dev \
--profile observability \
--profile glitchtip-dev \
--profile bullmq \
--profile wud \
config --quiet
shellcheck:
name: shellcheck (scripts/)
runs-on: ubuntu-24.04
timeout-minutes: 2
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Detect relevant changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
filters: |
code:
- 'infra/compose/**'
- 'scripts/**'
- 'setup.sh'
- '.github/workflows/**'
- name: ShellCheck
if: steps.filter.outputs.code == 'true'
run: |
# Strict: error on warnings, follow `source` directives.
# Root gate scripts (scripts/ci/*, stack-*.sh, setup.sh) are the
# repo's primary local defense — they get the same bar as infra.
shellcheck -x -S warning \
infra/compose/compose/dev.sh \
infra/compose/scripts/*.sh \
scripts/*.sh \
scripts/ci/*.sh \
setup.sh
prod-image-build:
name: build prod images (sanity)
runs-on: ubuntu-24.04
timeout-minutes: 15
# Cheaper than booting the prod profile end-to-end, but still
# exercises the actual prod Dockerfiles. Catches broken
# multi-stage builds, missing build deps, or registry-auth
# changes before main, instead of leaving the first breakage to
# the post-merge release workflow.
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Detect relevant changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
filters: |
code:
# Dockerfile.prod runs COPY . . — the whole app tree is
# image input, so app-only source changes must rebuild too.
- 'apps/api/**'
- 'apps/ui/**'
- 'infra/compose/**'
- '.github/workflows/infra-compose-validate-compose.yml'
- name: Set up Docker Buildx
if: steps.filter.outputs.code == 'true'
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Build apps/api prod image
if: steps.filter.outputs.code == 'true'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: apps/api
file: apps/api/Dockerfile.prod
push: false
load: true
tags: boringstack-api:pr-build
cache-from: type=gha,scope=apps-api-prod
cache-to: type=gha,scope=apps-api-prod,mode=max
- name: Build apps/ui prod image
if: steps.filter.outputs.code == 'true'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: apps/ui
file: apps/ui/Dockerfile.prod
push: false
load: true
tags: boringstack-ui:pr-build
cache-from: type=gha,scope=apps-ui-prod
cache-to: type=gha,scope=apps-ui-prod,mode=max
build-args: |
VITE_API_URL=https://api.example.test
yamllint:
name: yamllint (compose + workflows)
runs-on: ubuntu-24.04
timeout-minutes: 2
env:
# Single source for the yamllint pin — the local pre-push gate
# (infra/compose/scripts/pre-push.sh) reads this value at runtime to
# warn when the brew-installed yamllint diverges from CI.
YAMLLINT_VERSION: "1.38.0"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Detect relevant changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
filters: |
code:
- 'infra/compose/**'
- 'scripts/**'
- '.github/workflows/**'
- name: yamllint
if: steps.filter.outputs.code == 'true'
run: |
pip install --user "yamllint==${YAMLLINT_VERSION}"
# Document syntax (line length, indentation, truthy values) but
# don't fail on style nits — only on real syntax errors.
~/.local/bin/yamllint -d "{extends: relaxed, rules: {line-length: disable}}" \
infra/compose/compose/ .github/workflows/