toni
toni

Reputation: 152

Using base Images for Services in docker-compose with different args

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.

Upvotes: 1

Views: 1002

Answers (2)

David Maze
David Maze

Reputation: 160023

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.

Upvotes: 1

Turing85
Turing85

Reputation: 20205

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
    ...

Upvotes: 3

Related Questions