A new contributor's first day stalls on the same five breakages, year after year: a port already in use, a poisoned dependency cache, an .env that drifted from .env.example, a seed script that races the database, and a base image built for the wrong CPU architecture. This guide gives each one a deterministic diagnosis, a copy-paste fix, and a drift check so engineers can self-serve instead of escalating. It is part of the broader work on onboarding architecture and friction mapping, and the most acute single-symptom cases — containers that exit immediately on startup and DNS resolution failures between local containers — get dedicated walkthroughs.

Prerequisites

  • Docker Engine 24+ with the Compose v2 plugin (docker compose version).
  • jq, lsof, and pg_isready available on the host (brew install jq lsof postgresql on macOS; the Postgres client package on Linux).
  • A repository with a docker-compose.yml, a committed lockfile, and a .env.example.

Port Allocation & Service Binding Conflicts

Hardcoded ports and implicit host bindings cause silent collisions that stop a service before it logs anything useful. Before bringing the stack up, audit what is already listening.

  1. List current listeners so you can spot the conflict:
    #!/usr/bin/env bash
    set -euo pipefail
    lsof -iTCP -sTCP:LISTEN -P -n
  2. Bind explicitly to loopback and parameterize the port so two stacks never fight over 3000:
    # docker-compose.yml
    services:
      app:
        container_name: local-app-primary
        build: .
        ports:
          - "127.0.0.1:${APP_PORT:-3000}:3000"
        environment:
          APP_PORT: "${APP_PORT:-3000}"
  3. Run the drift check and reset stale bindings if the runtime mapping diverges from intent:
    #!/usr/bin/env bash
    set -euo pipefail
    RUNTIME_PORTS=$(docker ps --format '{{.Ports}}' --filter "name=local-app-primary")
    EXPECTED_PORTS="127.0.0.1:${APP_PORT:-3000}->3000/tcp"
    if [ "$RUNTIME_PORTS" != "$EXPECTED_PORTS" ]; then
      echo "Port drift detected; recreating containers." >&2
      docker compose down --remove-orphans
      docker compose up -d
    fi

The full Compose-specific fix for bind: address already in use lives in fixing "port is already allocated" errors in Compose.

Dependency Resolution & Cache Poisoning

Unpinned transitive dependencies and a stale package cache produce non-deterministic builds: the same git clone yields a different node_modules on two laptops. Pin exact versions in lockfiles and validate integrity.

  1. Install strictly from the lockfile and record its hash inside the dev container:
    // .devcontainer/devcontainer.json
    {
      "name": "Local Dev Environment",
      "postCreateCommand": "npm ci --prefer-offline && sha256sum package-lock.json",
      "mounts": [
        "source=npm-cache,target=/home/node/.npm,type=volume"
      ]
    }
  2. Diff the resolved tree against a known-good baseline and fail on divergence:
    #!/usr/bin/env bash
    set -euo pipefail
    npm ls --all --json > local-tree.json
    if ! diff -q ci-baseline-tree.json local-tree.json; then
      echo "Dependency drift detected versus CI baseline." >&2
      exit 1
    fi

When a build wedges on a transitive conflict, trace it with detecting circular dependencies in local builds and the broader dependency tree visualization workflow.

Apple Silicon (ARM64): native modules (node-gyp, grpc) often fail to compile without architecture flags; set npm_config_arch=arm64 or --build-from-source in .npmrc. macOS (Docker Desktop): the :cached/:delegated mount consistency flags can mask a poisoned cache; mount lockfiles read-only and rebuild from a clean volume to confirm. WSL2: keep node_modules on the Linux filesystem; the /mnt/c translation layer corrupts symlinks. Set core.autocrlf=false.

Environment Variable & Secret Drift

When local .env falls behind .env.example, services boot with missing keys and fail at the first call that needs them. Enforce completeness before execution.

  1. Add a pre-commit hook that rejects an incomplete .env:
    # .pre-commit-config.yaml
    repos:
      - repo: local
        hooks:
          - id: check-env
            name: Validate .env against schema
            entry: bash -c 'jq -e ".required | all(. as $k | env | has($k))" .env.schema.json'
            language: system
            files: '\.env$'
  2. Surface the drift explicitly during diagnosis:
    #!/usr/bin/env bash
    set -euo pipefail
    if ! diff -y --suppress-common-lines .env.example .env; then
      echo "Environment variable drift detected; reconcile required keys." >&2
    fi

For schema-driven generation and validation that prevents this class entirely, see catching missing env vars before container startup.

WSL2: CRLF line endings make .env parsers fail silently; run dos2unix .env or set Git core.eol=lf. macOS (Docker Desktop): .env resolves relative to the Compose project root, but docker run resolves it against the working directory — always pass --env-file with an explicit path.

Database State & Seed Script Execution Failures

Non-idempotent init scripts and missing readiness gates cause race conditions where the app connects before the schema exists. Gate startup on a health probe and make seeds deterministic.

  1. Block the app until the database is healthy:
    # docker-compose.yml
    services:
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_PASSWORD: "${DB_PASS:-localdev}"
          POSTGRES_DB: app_db
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres"]
          interval: 2s
          timeout: 5s
          retries: 5
      app:
        build: .
        depends_on:
          db:
            condition: service_healthy
  2. Run an idempotent, readiness-gated seed:
    #!/usr/bin/env bash
    set -euo pipefail
    export PGPASSWORD="${POSTGRES_PASSWORD:-localdev}"
    until pg_isready -h db -p 5432 -U postgres; do
      echo "Waiting for PostgreSQL readiness..."
      sleep 1
    done
    psql -h db -U postgres -d app_db -f /docker-entrypoint-initdb.d/01-schema.sql
    psql -h db -U postgres -d app_db -f /docker-entrypoint-initdb.d/02-seed.sql

Startup-order races between healthy and dependent services are covered in depth in resolving service startup order and healthcheck races.

WSL2: bind-mounted DB volumes suffer severe I/O latency; use named volumes or a path under ~/, never /mnt/c. Apple Silicon (ARM64): postgres:16-alpine is multi-arch, but x86-only extensions (some PostGIS builds) require platform: linux/amd64 and emulation.

Cross-Architecture Container Runtime Mismatch

Pulling an image built for the wrong architecture, or compiling a native extension for the wrong target, yields exec format error. Detect host architecture early and map it to a build platform.

  1. Make the build architecture-aware:
    # Dockerfile
    ARG TARGETARCH=amd64
    FROM --platform=linux/${TARGETARCH} node:20-alpine
    RUN apk add --no-cache python3 make g++ gcc
  2. Diagnose a mismatch and rebuild for the host:
    #!/usr/bin/env bash
    set -euo pipefail
    CONTAINER_ARCH=$(docker inspect --format='{{.Architecture}}' local-app-primary)
    HOST_ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
    if [ "$CONTAINER_ARCH" != "$HOST_ARCH" ]; then
      echo "Architecture mismatch: container=$CONTAINER_ARCH host=$HOST_ARCH; rebuilding." >&2
      docker compose build --build-arg "TARGETARCH=$HOST_ARCH"
    fi

Apple Silicon (ARM64): Docker Desktop defaults to linux/arm64; emulating linux/amd64 via Rosetta 2/QEMU is slow, so prefer native multi-arch base images. WSL2: uname -m reports x86_64 regardless of the underlying CPU; use dpkg --print-architecture for accurate detection in scripts.

Rollback / Recovery

When a diagnostic fix leaves the stack in a worse state, return to a clean baseline in one move:

#!/usr/bin/env bash
set -euo pipefail
docker compose down -v --remove-orphans
docker network prune -f
git checkout -- docker-compose.yml .env.example
docker compose up -d --wait

This tears down containers and their volumes, clears dangling networks, restores tracked config, and brings a fresh stack up gated on health.