skip to Main Content

My Setup:
I have 3 Services defined in my docker-compose.yml: frontend backend and postgresql. postgresql is pulled from docker-hub.

frontend and backend are built from their own Dockerfiles, most of the Code of these Dockerfiles is the same and only EXPOSE ENTRPOINT CMD and ARG-Values differ from each other. That is why I wanted to create a ‘base-Dockerfile’ that these two Services can "include".
Sadly I found out I can not simply "include" a Dockerfile into another Dockerfile, I have to create an Image.
So I tried to create a base image for frontend and backend in my docker-compose.yml:

services:
  frontend_base:
    image: frontend_base_image
    build:
      context: ./
      dockerfile: base.dockerfile
      args:
        - WORKDIR=/app/frontend/
        - TOOLSDIR=${PWD}/docker/tools
        - LOCALDIR=${PWD}/app/frontend/client

  backend_base:
    image: backend_base_image
    build:
      context: ./
      dockerfile: base.dockerfile
      args:
        - WORKDIR=/app/backend/
        - TOOLSDIR=${PWD}/docker/tools
        - LOCALDIR=${PWD}/app/backend/api

  frontend:
    depends_on:
      - frontend_base
    # Some more stuff for the service
  backend:
    depends_on:
      - backend_base
    # Some more stuff for the service

My ‘base-Dockerfile’:

FROM node:18

# Set in docker-compose.yml-file
ARG WORKDIR
ARG TOOLSDIR
ARG LOCALDIR
ENV WORKDIR=${WORKDIR}

# Install dumb-init for the init system
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64
RUN chmod +x /usr/local/bin/dumb-init

WORKDIR ${WORKDIR}
RUN mkdir -p ${WORKDIR}

# Copy package.json to the current workdir (for npm install)
COPY ${LOCALDIR}/package*.json ${WORKDIR}

# Install all Packages (refereed from package.json)
RUN npm install

COPY ${TOOLSDIR}/start.sh /usr/local/bin/start.sh
COPY ${LOCALDIR}/ ${WORKDIR}

The Problem I am facing:
My frontend and backend Dockerfiles try to pull the ‘base-image’ from docker.io

 => ERROR [docker-backend internal] load metadata for docker.io/library/backend_base_image:latest                                                                                 0.9s
 => ERROR [docker-frontend internal] load metadata for docker.io/library/frontend_base_image:latest                                                                               0.9s
 => CANCELED [frontend_base_image internal] load metadata for docker.io/library/node:18

My Research:
I do not know if my approach is possible, I did not find much Resources about this (integrated with docker-compose) online, only Resources about building the Images via Shell and then using them in a Dockerfile. I also tried this and ran into some other issues, where I could not provide correct arguments to the base-Dockerfile.
So I firstly wanted to find out if it is possible with docker-compose.

I am sorry if this is super obvious and my Question is dumb, I am relatively new to Docker.

2

Answers


  1. We could use the feature of a multistage containerfile to define all three images in a single containerfile:

    FROM node:18 AS base
    
    # Set in docker-compose.yml-file
    ARG WORKDIR
    ARG TOOLSDIR
    ARG LOCALDIR
    ENV WORKDIR=${WORKDIR}
    
    # Install dumb-init for the init system
    RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64
    RUN chmod +x /usr/local/bin/dumb-init
    
    WORKDIR ${WORKDIR}
    RUN mkdir -p ${WORKDIR}
    
    # Copy package.json to the current workdir (for npm install)
    COPY ${LOCALDIR}/package*.json ${WORKDIR}
    
    # Install all Packages (refereed from package.json)
    RUN npm install
    
    COPY ${TOOLSDIR}/start.sh /usr/local/bin/start.sh
    COPY ${LOCALDIR}/ ${WORKDIR}
    
    FROM base AS frontend
    ...
    
    FROM base AS backend
    ...
    

    In our docker-compose.yml, we can then build a specific stage for the frontend– and backend-service:

      ...
      frontend:
        image: frontend
        build:
          context: ./
          target: frontend
          dockerfile: base.dockerfile
        ...
      backend:
        image: backend
        build:
          context: ./
          target: backend
          dockerfile: base.dockerfile
        ...
    
    Login or Signup to reply.
  2. If you want a single base image with shared tools, you can do this almost exactly the way you describe; but the one caveat is that you can’t describe the base image in the docker-compose.yml file. You need to run separately from Compose

    docker build -t base-image -f base.dockerfile .
    

    I would not try to install any application code in that base Dockerfile. Where you for example install an init wrapper that needs to be shared across all of your application images, that does make sense. I think it’s fine to tie a Dockerfile to a specific source-tree and image layout, and don’t typically recommend passing filesystem paths as ARGs.

    # base.dockerfile
    FROM node:18
    
    RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 
     && chmod +x /usr/local/bin/dumb-init
    
    COPY docker/tools/start.sh /usr/local/bin/
    
    ENTRYPOINT ["dumb-init", "--"]
    CMD ["start.sh"]
    

    The per-image Dockerfiles will look pretty similar – and like every other Node Dockerfile – but there’s no harm in repeating this, in much the same way that your components probably have similar-looking but self-contained package.json files.

    # */Dockerfile
    FROM base-image
    
    WORKDIR /app  # also creates it
    COPY package*.json ./
    RUN npm ci
    COPY ./ ./
    RUN npm build
    
    EXPOSE 3000
    # CMD ["npm", "run", "start"]  # if the start.sh from the base is wrong
    

    Of note, this gives you some flexibility to change things if the two image setups aren’t identical; if you need an additional build step, or if you want to run a dev server, or package the frontend into a lighter-weight Nginx server.

    In the Compose file you’d declare these normally with a build: block. Compose isn’t aware of the base image and there’s no way to tell it about it.

    version: '3.8'
    services:
      frontend:
        build: ./app/frontend/client
        ports: ['3000:3000']
      backend:
        build: ./app/backend/api
        ports: ['3001:3000']
    

    One thing I’ve done here which at least reduces the number of variable references is to consistently use . as the current directory name. In the Compose file that’s the directory containing the docker-compose.yml; on the left-hand side of COPY it’s the build: context directory on the host; on the right-hand side of COPY it’s the most recent WORKDIR. Using . where appropriate means you don’t have to repeat the directory name, so you do have a little flexibility if you do need to rearrange your source tree or container filesystem.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search