Search code examples
dockerdocker-composedocker-multi-stage-build

Docker multi-stage build cannot find file in second stage


My environment is: Ubuntu 18.04 Docker 19.03.11

I am working on a Flask app and am managing services with Docker. Here is my compose file:

version: '3.6'

x-build-args: &build_args
  INSTALL_PYTHON_VERSION: 3.8
  INSTALL_NODE_VERSION: 12

x-default-volumes: &default_volumes
  volumes:
    - ./:/app
    - node-modules:/app/node_modules
    - ./dev.db:/tmp/dev.db

services:
  flask-dev:
    build:
      context: .
      target: development
      args:
        <<: *build_args
        user: abc
    image: "testapp-development"
    ports:
      - "5000:5000"
      - "2992:2992"
    <<: *default_volumes

  flask-prod:
    build:
      context: .
      target: production
      args:
        <<: *build_args
    image: "testapp-production"
    ports:
      - "5000:5000"
    environment:
      FLASK_ENV: production
      FLASK_DEBUG: 0
      LOG_LEVEL: info
      GUNICORN_WORKERS: 4
    <<: *default_volumes

  manage:
    build:
      context: .
      target: manage
    environment:
      FLASK_ENV: production
      FLASK_DEBUG: 0
    image: "testapp-manage"
    stdin_open: true
    tty: true
    <<: *default_volumes

volumes:
  node-modules:
  static-build:
  dev-db:

My Dockerfile:

# ==================================== BASE ====================================
ARG INSTALL_PYTHON_VERSION=${INSTALL_PYTHON_VERSION:-3.7}
FROM python:${INSTALL_PYTHON_VERSION}-slim-buster AS base

RUN apt-get update
RUN apt-get install -y \
    curl \
    gcc

ARG INSTALL_NODE_VERSION=${INSTALL_NODE_VERSION:-12}
RUN curl -sL https://deb.nodesource.com/setup_${INSTALL_NODE_VERSION}.x | bash -
RUN apt-get install -y \
    nodejs \
    && apt-get -y autoclean

ARG user=sid
# Add the user and their group
RUN groupadd -r ${user} && useradd -m -r -l -g ${user} ${user}
# the /app directory contains things that npm needs
# user won't have permissions to this, so copy it
# into their home dir
WORKDIR /app
COPY --chown=${user}:${user} . /home/${user}/
USER ${user}
WORKDIR /home/${user}/app
ENV PATH="/home/${user}/.local/bin:${PATH}"
RUN npm install

# ================================= DEVELOPMENT ================================
FROM base AS development
RUN pip install --user -r requirements/dev.txt
EXPOSE 2992
EXPOSE 5000
CMD [ "npm", "start" ]

# ================================= PRODUCTION =================================
FROM base AS production
RUN pip install --user -r requirements/prod.txt
COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY supervisord_programs /etc/supervisor/conf.d
EXPOSE 5000
ENTRYPOINT ["/bin/bash", "shell_scripts/supervisord_entrypoint.sh"]
CMD ["-c", "/etc/supervisor/supervisord.conf"]

# =================================== MANAGE ===================================
FROM base AS manage
RUN pip install --user -r requirements/dev.txt
ENTRYPOINT [ "flask" ]

Both the development and production target are failing

Here is the output I'm getting from running docker-compose build flask-dev:

Building flask-dev
Step 1/20 : ARG INSTALL_PYTHON_VERSION=${INSTALL_PYTHON_VERSION:-3.7}
Step 2/20 : FROM python:${INSTALL_PYTHON_VERSION}-slim-buster AS base
 ---> 38cd21c9e1a8
Step 3/20 : RUN apt-get update
 ---> Using cache
 ---> 5d431961b77a
Step 4/20 : RUN apt-get install -y     curl     gcc
 ---> Using cache
 ---> caeafb0035dc
Step 5/20 : ARG INSTALL_NODE_VERSION=${INSTALL_NODE_VERSION:-12}
 ---> Using cache
 ---> 6e8a1eb59d3c
Step 6/20 : RUN curl -sL https://deb.nodesource.com/setup_${INSTALL_NODE_VERSION}.x | bash -
 ---> Using cache
 ---> 06fe96eb13e7
Step 7/20 : RUN apt-get install -y     nodejs     && apt-get -y autoclean
 ---> Using cache
 ---> 1e085132d325
Step 8/20 : ARG user=sid
 ---> Using cache
 ---> 3b3faf180389
Step 9/20 : RUN groupadd -r ${user} && useradd -m -r -l -g ${user} ${user}
 ---> Running in 9672d6f8b64d
Removing intermediate container 9672d6f8b64d
 ---> 02a5c2602513
Step 10/20 : WORKDIR /app
 ---> Running in bd48ac652908
Removing intermediate container bd48ac652908
 ---> 92c16bf347d4
Step 11/20 : COPY --chown=${user}:${user} . /home/${user}/
 ---> 2af997c5a255
Step 12/20 : USER ${user}
 ---> Running in 6409cf8784ee
Removing intermediate container 6409cf8784ee
 ---> 012d9cf92f31
Step 13/20 : WORKDIR /home/${user}/app
 ---> Running in a12b164c39dd
Removing intermediate container a12b164c39dd
 ---> db6fba37f948
Step 14/20 : ENV PATH="/home/${user}/.local/bin:${PATH}"
 ---> Running in eb13f4786b17
Removing intermediate container eb13f4786b17
 ---> a32249e3169b
Step 15/20 : RUN npm install
 ---> Running in 8aefdd56e8f2
npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~2.1.2 (node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.2.7 (node_modules/watchpack-chokidar2/node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

audited 712 packages in 2.873s

43 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Removing intermediate container 8aefdd56e8f2
 ---> 737faa9b974e

Step 16/20 : FROM base AS development
 ---> 737faa9b974e
Step 17/20 : RUN pip install --user -r requirements/dev.txt
 ---> Running in af5508643877
ERROR: Could not open requirements file: [Errno 2] No such file or directory: 'requirements/dev.txt'
ERROR: Service 'flask-dev' failed to build: The command '/bin/sh -c pip install --user -r requirements/dev.txt' returned a non-zero code: 1

I think my approach is wrong to this. I need to have the Flask app work out of the app volume that I'm using in the compose file.

What I think I'm misunderstanding is the multi-stage builds. Do I need to do extra steps in the development, production, and manage stages in order to be able to reach the /home/${user}/app directory? From what I understand ARGs defined in previous stages are not available in subsequent ones, but is all the processing done in a stage also not available in subsequent stages?

I have also tried using COPY --from=base in the second stage with the full path to where the app directory sits during the base build stage, but this isn't correct either.

The relevant parts of project directory are:

testapp/
    docker-compose.yml
    Dockerfile
    testapp/
       <web app code>
    requirements/
        dev.txt
        prod.txt

Solution

  • You've copied your files into a different directory:

    WORKDIR /app
    COPY --chown=${user}:${user} . /home/${user}/
    USER ${user}
    WORKDIR /home/${user}/app
    

    The requirements directory will be in /home/${user}/ while your relative run command will use the workdir to look in /home/${user}/app. You likely want to copy into the app subdirectory:

    COPY --chown=${user}:${user} . /home/${user}/app