Dockerfiles run, but docker-compose won't. So they are building correctly, but I likely goofed on a config

So I have two questions and I’d be very grateful for anyone’s assistance.

First problem is why do my images work as desired, but for some reason they don’t work when I’m expecting the docker-compose file to run them. I believe it’s likely a tiny config issue, but on docker-compose any issue is a big issue and if someone could you help me out I’d appreciate it. Documentation and can be found immediately following the second question.

Second problem is more a question of being professional. How would you improve my Dockerfiles and compose file and if so, then why do you suggest what you suggest? I’m just trying to improve the quality and it’s not an easy thing. Your assistance is greatly appreciated.

Gist link for those who prefer it: https://gist.github.com/misterhtmlcss/ef4dd25f9d964849255034613650bd80
#1 Problem.
location: ./server
Item: Dockerfile1 (server)

FROM node:12.18.4-alpine as build

RUN apk --no-cache add --virtual native-deps \
  g++ gcc libgcc libstdc++ linux-headers autoconf automake make nasm python git && \
  npm install --quiet node-gyp -g

RUN mkdir -p /src/app/node_modules && chown -R node:node /src/app
WORKDIR /src/app


COPY package*.json ./
RUN npm install

COPY . ./

FROM node:12.18.4-alpine
COPY --from=build . ./

ARG PORT=5000
ENV PORT=$PORT
EXPOSE $PORT

CMD [ "npm", "run", "dev" ]

location: ./client
Item: Dockerfile2 (client)

FROM node:12.18.4-alpine

RUN mkdir -p /src/app/node_modules && chown -R node:node /src/app
WORKDIR /src/app


COPY package*.json ./
RUN npm install

COPY . ./

ARG PORT=3000
ENV PORT=$PORT
EXPOSE $PORT

CMD [ "npm", "start" ]

location: ./ (root)
Item: docker-compose

version: '3.8'

services:
  server:
    image: server
    container_name: server
    build:
      context: ./server
      args:
        - NODE_ENV=development
      dockerfile: Dockerfile.dev
    ports:
      - "5000:5000"
    volumes:
      - .:/src/app
      - /src/app/node_modules
  client:
    image: client
    container_name: client
    build:
      context: ./client
      args:
        - NODE_ENV=development
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/src/app
      - /src/app/node_modules

Regarding your first question: are you aware of the differences between volumes and bind-mounts? While volumes have a copy mechanism that copies existing data from a container folder back to the volume, bind-mounts mount a host folder on top of a container folder, thus making its original content invisible.

The .:/src/app volume mapping is actualy a bind-mount.
I am surprised that /src/app/nodes_modules is even a valid mount option. You might want to use a named volume here and do a proper volume mapping; do not forget to declare the volume in your compose.yml.

Regarding your second question: you might want to introduce separation of concerns and perform the image builds within a ci/cd pipeline using docker build ... to explicitly build tagged images, which are pushed to a (container image registry. Then use the docker-compose file for pure deployement configuration purposes and address the previously build images there. Be aware that docker-compose will not pull new images for mutable tag once an image for that particular tag is present in the local image cache - you will need to explicitly pull to use the most recent images. Docker swarm on the other hand always pulls the most recent image during container deployment.

Dockerfile1: since this is a mutlistage build, the instuctions for the “main” stage look about right. ARG, ENV and EXPOSE are cheap directives, thus they can remain at the bottom, even though they are less likely to change. I won’t discuss the build stage as it has no effect on the final image. What is the context of the main stage’s COPY --from=build . ./ operation? What is ‘.’ from the perspective of the main image? Does it realy just copy the files you intend to copy and realy need? Also the CMD includes a environment specific arguement… wouldn’t it be better to introdue an environment variable and leverage it in your app (or entrypoint script?) to determin the environment?

Dockerfile2: same questions from Dockerfile1, except the CMD, which looks fine here - even though no ENV is desclared.

Dockerfile1 & Dockerfile2: I know it’s a matter of taste, but don’t you agree by not adding a VOLUME instruction, the information which path(s) inside the container are ment to be used with volumes is lost? If a VOLUME instruction is present and no volume is mounted, docker will create an anonymous volume for this folder(s).

Generally: you might want add image labels according https://github.com/opencontainers/image-spec/blob/master/annotations.md. You could use the to transport details about your image like the version of the embedded application, the commit id, build date and whatever you find reasonable.

Does that make sense?

Hi Metin,

Thank you so much for taking the time to assist me. I really appreciated it. I have solved it; the link below is also the same link above, but I’ve re-included here for your convenience and perhaps you’ll have time to give feedback.

I’m still reading and re-reading your feedback!! I am not sure I’ve integrated any of it into my work, but I’ll try. It’s just hard for me to follow easily because I’m still so new; I’ll get there. :slight_smile:

Link: https://gist.github.com/misterhtmlcss/ef4dd25f9d964849255034613650bd80

All the best this weekend. If you are in the snow like us, then drive safe.

Cheers.

It’s not much fun to provide feedback on changing configurations.
How about you incorpate everything and keep on optimizing until you hit an impediment :slight_smile:

Also: the way you build the image does not levarage the build cache optimaly. Changing parts should be in the bottom. Also loose your ARG and ENV for the port and use a static port for EXPOSE. The EXPOSE instruction does nothing byitself - it is purely for documentation purpose (and container links, which you don’t use). Whenever an ARG is used it will lead to a cache miss in your build cache, and new image layers will be created for this instruction and all consequetive instructions.