Home > Software design >  Why does docker compose have a different behavior?
Why does docker compose have a different behavior?

Time:11-02

I have a NestJS project that uses TypeORM with a MySQL database. I dockerized it using docker compose, and everything works fine on my machine (Mac). But when I run it from my remote instance (Ubuntu 22.04) I got the following error:

server    | yarn run v1.22.19
server    | $ node dist/main
server    | node:internal/modules/cjs/loader:998
server    |   throw err;
server    |   ^
server    | 
server    | Error: Cannot find module '/usr/src/app/dist/main'
server    |     at Module._resolveFilename (node:internal/modules/cjs/loader:995:15)
server    |     at Module._load (node:internal/modules/cjs/loader:841:27)
server    |     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
server    |     at node:internal/main/run_main_module:23:47 {
server    |   code: 'MODULE_NOT_FOUND',
server    |   requireStack: []
server    | }
server    | 
server    | Node.js v18.12.0
server    | error Command failed with exit code 1.
server    | info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
server exited with code 1

Here is my Dockerfile:

FROM node:18-alpine AS development

# Create app directory
WORKDIR /usr/src/app

# Copy files needed for dependencies installation
COPY package.json yarn.lock ./ 

# Disable postinstall script that tries to install husky
RUN npx --quiet pinst --disable

# Install app dependencies
RUN yarn install --pure-lockfile

# Copy all files
COPY . .

# Increase the memory limit to be able to build
ENV NODE_OPTIONS=--max_old_space_size=4096
ENV GENERATE_SOURCEMAP=false

# Entrypoint command
RUN yarn build


FROM node:18-alpine AS production

# Set env to production
ENV NODE_ENV=production

# Create app directory
WORKDIR /usr/src/app

# Copy files needed for dependencies installation
COPY package.json yarn.lock ./ 

# Disable postinstall script that tries to install husky
RUN npx --quiet pinst --disable

# Install app dependencies
RUN yarn install --production --pure-lockfile

# Copy all files
COPY . .

# Copy dist folder generated in development stage
COPY --from=development /usr/src/app/dist ./dist

# Entrypoint command
CMD ["node", "dist/main"]

And here is my docker-compose.yml file:

version: "3.9"

services:
  server:
    container_name: blognote_server
    image: bladx/blognote-server:latest
    build:
      context: .
      dockerfile: ./Dockerfile
      target: production
    environment:
      RDS_HOSTNAME: ${MYSQL_HOST}
      RDS_USERNAME: ${MYSQL_USER}
      RDS_PASSWORD: ${MYSQL_PASSWORD}
      JWT_SECRET: ${JWT_SECRET}
    command: yarn start:prod
    ports:
      - "3000:3000"
    networks:
      - blognote-network
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    links:
      - mysql
    depends_on:
      - mysql
    restart: unless-stopped
  mysql:
    container_name: blognote_database
    image: mysql:8.0
    command: mysqld --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    ports:
      - "3306:3306"
    networks:
      - blognote-network
    volumes:
      - blognote_mysql_data:/var/lib/mysql
    restart: unless-stopped

networks:
    blognote-network:
      external: true

volumes:
  blognote_mysql_data:

Here is what I tried to do:

  • I cleaned everything on my machine and then run docker compose --env-file .env.docker up but this did work.
  • I run my server image using docker (not docker compose) and it did work too.
  • I tried to make a snapshot then connect to it and run node dist/main manually, but this also worked.

So I don't know why I'm still getting this error. And why do I have a different behavior using docker compose (on my remote instance)? Am I missing something?

CodePudding user response:

Your docker-compose.yml contains two lines that hide everything the image does:

volumes:
  # Replace the image's `/usr/src/app`, including the built
  # files, with content from the host.
  - .:/usr/src/app

  # But: the `node_modules` directory is user-provided content
  # that and needs to be persisted separately from the container
  # lifecycle.  Keep that tree in an anonymous volume and never
  # update it, even if it changes in the image or the host.
  - /usr/src/app/node_modules

You should delete this entire block.

You will see volumes: blocks like that that try to simulate a local-development environment in an otherwise isolated Docker container. This will work only if the Dockerfile only COPYs the source code into the image without modifying it at all, and the node_modules library tree never changes.

In your case, the Dockerfile produces a /usr/src/app/dist directory in the image which may not be present on the host. Since the first bind mount hides everything in the image's /usr/src/app directory, you don't get to see this built tree; and your image is directly running node on that built application and not trying to simulate a local development environment. The volumes: don't make sense here and cause problems.

  • Related