Search code examples
node.jsdockernpmnext.jsdocker-compose

'docker compose up' with NextJS app results in 'The npm ci command can only install with an existing package-lock.json'


Problem Statement: Doing a 'sudo docker compose -f compose/docker-compose.yaml up' on a new VM for a NextJs application docker container, results in:

> [... builder 4/6] RUN npm ci:                                                                                                               
1.721 npm error code EUSAGE                                                                                                                                     
1.722 npm error                                                                                                                                                 
1.722 npm error The `npm ci` command can only install with an existing package-lock.json or                                                                     
1.722 npm error npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or                                                                     
1.722 npm error later to generate a package-lock.json file, then try again.
1.722 npm error
1.722 npm error Clean install a project
1.722 npm error
1.722 npm error Usage:
1.722 npm error npm ci
1.722 npm error
1.722 npm error Options:
1.722 npm error [--install-strategy <hoisted|nested|shallow|linked>] [--legacy-bundling]
1.722 npm error [--global-style] [--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
1.722 npm error [--include <prod|dev|optional|peer> [--include <prod|dev|optional|peer> ...]]
1.722 npm error [--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--no-audit]
1.722 npm error [--no-bin-links] [--no-fund] [--dry-run]
1.722 npm error [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
1.722 npm error [-ws|--workspaces] [--include-workspace-root] [--install-links]
1.722 npm error
1.722 npm error aliases: clean-install, ic, install-clean, isntall-clean
1.722 npm error
1.722 npm error Run "npm help ci" for more info

Preface: I've seen similar posts relating to Github actions, but haven't seen any pertaining to Docker that have solved my problem.

I'm trying to deploy a next js application (running in a docker container) to an AWS VM. The steps I took leading to this error are:

1. Locally on my PC, do a 'docker compose -f compose/docker-compose.yaml up'. The files within the compose folder look like this:

//.dockerignore:

.env
Dockerfile.prod
.dockerignore
.next
.git
.gitignore
node_modules
npm-debug.log
README.md

//DockerFile.prod:

# Development Stage
FROM node:20.18.1-alpine AS development
WORKDIR /app
COPY package*.json package-lock.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]

# Builder Stage
FROM node:20.18.1-alpine AS builder
WORKDIR /app
COPY package*.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production Stage 
FROM node:20.18.1-alpine AS production
WORKDIR /app
# Copy the built artifacts from the builder stage
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Set the environment variables (if needed)
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]

//docker-compose.yaml:

services:
  web-app:
    build:
      context: ../
      dockerfile: compose/DockerFile.prod
      target: production
      args:
        NEXT_PUBLIC_CLIENTVAR: "clientvar"
    ports:
      - 3000:3000
    environment:
      - NODE_ENV=production
    depends_on:
      - db
    restart: unless-stopped
  db:
    image: postgres:14.15
    container_name: postgres
    ports:
      - 5433:5432
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: postgres
    volumes:
      - postgres-data:/var/lib/postgresql/data
    restart: unless-stopped
volumes:
  postgres-data:

2. Tag the web application and push it to Dockerhub container repository.

3. Install docker on the AWS VM + login

4. Pull the web application container from dockerhub

5. Create the /compose folder and docker files on the VM.

6. Run 'sudo docker compose -f compose/docker-compose.yaml up'

The `npm ci` command can only install with an existing package-lock.json or                                                                   npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or                                                                     later to generate a package-lock.json file, then try again.

Solution

  • There are two directives in the Compose file that indicate which image to run. You can build: an image from local sources, or you can run a prebuilt image: from elsewhere. Your Compose file has a build: directive, so it always builds from local sources; it does not use the image you've built and pushed.

    The first easy change is to replace the entire build: block with an image: reference.

    version: '3.8'
    services:
      web-app:
        image: myname/webapp:${WEBAPP_TAG:-latest}
        ports:
          - 3000:3000
        depends_on:
          - db
        restart: unless-stopped
      db: { ... }  # same as above
    

    Here I've allowed providing the tag as an environment variable, so that your CI system can produce uniquely-named builds and you can upgrade (or downgrade) by specifying a tag name from an environment variable.

    In your local setup where you do in fact want to build from source, you need a second Compose file. Compose supports multiple input files and merges their contents. If your current Compose file is named docker-compose.yml then the tool will automatically find docker-compose.override.yml; if you are using a newer version of Compose with just compose.yml then it will find compose.override.yml.

    The override file can contain only the build: declaration. All of the other settings will be taken from the main Compose file. This includes image:, which here gives a name to the image that's built.

    # docker-compose.override.yml
    version: 3.8
    services:
      web-app:
        build:
          context: ../
          dockerfile: compose/DockerFile.prod
          target: production
          args:
            NEXT_PUBLIC_CLIENTVAR: "clientvar"
    

    Then on your local system you can build and push an image

    export WEBAPP_TAG=$(date +%Y%m%d)  # `git rev-parse HEAD` is also a good choice
    docker-compose build
    docker-compose push
    

    and on the remote system run it (make sure the override file is not present there)

    WEBAPP_TAG=20250122 docker-compose up -d