Files
pnger/DOCKER_BUILD_FIX.md

4.3 KiB

Docker Build Fix - Automatic Lockfile Generation

Issue

Docker build was failing with the following error:

npm error The `npm ci` command can only install with an existing package-lock.json or
npm error npm-shrinkwrap.json with lockfileVersion >= 1.

Root Cause

The npm ci command requires package-lock.json files to be present. These lockfiles were missing from:

  • frontend/package-lock.json
  • backend/package-lock.json

Solution Applied

Dockerfile Enhancement

The Dockerfile now automatically generates lockfiles during the build process if they don't exist:

RUN if [ ! -f package-lock.json ]; then \
        echo "Generating package-lock.json..."; \
        npm install --package-lock-only; \
    fi && \
    npm ci

This approach:

  • Works whether lockfiles are committed or not
  • Uses npm ci for faster, deterministic builds
  • Auto-generates lockfiles on first build
  • Falls back gracefully if lockfiles are missing
  • No manual intervention required

Files Added/Modified

  1. Dockerfile - Updated with conditional lockfile generation in all three stages:

    • Frontend builder stage
    • Backend builder stage
    • Production runtime stage
  2. .dockerignore - Optimizes Docker build context

  3. frontend/package-lock.json - Stub lockfile (optional - auto-generated if missing)

  4. backend/package-lock.json - Stub lockfile (optional - auto-generated if missing)

How It Works

Build Flow

  1. Copy package.json into build stage
  2. Check for lockfile:
    • If exists → Use it directly with npm ci
    • If missing → Generate with npm install --package-lock-only, then use npm ci
  3. Install dependencies using the lockfile
  4. Build the application

Benefits

Approach Speed Deterministic Requires Lockfile
npm install Slow No No
npm ci (original) Fast Yes Required
Our solution Fast Yes Auto-generated

Usage

Build Docker Image

docker build -t pnger:latest .

The build will:

  • Automatically generate lockfiles if missing
  • Use existing lockfiles if present
  • Complete successfully either way

Run Container

docker run -d \
  -p 3000:3000 \
  -e MAX_FILE_SIZE=10485760 \
  --name pnger \
  pnger:latest

Unraid Deployment

The Docker image will build cleanly in Unraid's container manager without any additional configuration.

Optional: Commit Lockfiles for Faster Builds

While not required, you can commit lockfiles to skip the generation step:

# Generate lockfiles locally
cd frontend && npm install && cd ..
cd backend && npm install && cd ..

# Commit them
git add frontend/package-lock.json backend/package-lock.json
git commit -m "Add complete lockfiles for faster builds"

Benefits of committing lockfiles:

  • Slightly faster builds (skips generation step)
  • Guarantees exact same dependency versions across all builds
  • Enables dependency security audits in CI/CD

Drawback:

  • Must remember to update lockfiles when changing dependencies

Build Optimization

The .dockerignore file excludes:

  • node_modules/ (prevents copying local dependencies)
  • Development files (.env, .vscode/, etc.)
  • Build artifacts (only copied when needed from builder stages)
  • Documentation and test files

This reduces build context transfer time significantly.

Verification

Test the complete build:

# Build image
docker build -t pnger:test .

# Run container
docker run -d -p 3000:3000 --name pnger-test pnger:test

# Check health
curl http://localhost:3000

# View logs
docker logs pnger-test

# Cleanup
docker stop pnger-test && docker rm pnger-test

Technical Details

Multi-Stage Build

  1. frontend-builder: Builds Svelte/Vite frontend
  2. backend-builder: Compiles TypeScript backend
  3. production: Combines compiled outputs with production dependencies only

Security Features

  • Runs as non-root user (node)
  • Health check endpoint configured
  • Minimal production dependencies (--omit=dev)
  • Alpine Linux base (smaller attack surface)

Created: 2026-03-08
Branch: fix/add-package-lockfiles
Status: Ready to merge
Issue: Docker build failing with missing package-lock.json RESOLVED