7

I have a Dockerfile with multiple targets. For example:

FROM x as frontend
...

FROM y as backend
...

FROM z as runtime
...
COPY --from=frontend ...
COPY --from=backend ...

In order to build and tag the final image, I use:

docker build -t my-project .

To build and tag intermediary targets, I provide --target argument:

docker build -t my-project-backend --target backend .

But is it possible to build a final image and tag all the intermediary images as well? In other words, the same as :

docker build -t my-project-frontend --target frontend .
docker build -t my-project-backend --target backend .
docker build -t my-project .

But with a single command?

I think a bit of explanation required. If use buildkit (export DOCKER_BUILDKIT=1), then all independent targets are built in parallel. So it's simply faster than building them one by one. And I need to tag every target to push them to a docker registry as well as final one.

Currently I'm building my images in CI without buildkit and I'm trying to speed up the process a bit.

fbjorn
  • 367
  • 3
  • 18
  • 6
    Did you try building the final target (`runtime`) _first_ and only then the other ones? If the intermediate images are kept around (without being tagged) building them "again" would just use the cached layers and so be much faster and will in the best case just add a tag to the existing (untagged) images. – acran Dec 29 '20 at 16:38
  • Don't use the `--target`-flag for unrelated images. Each image should have a own Dockerfile - expect they're using the same files (so they gain from docker-layer-caching). – akop Jan 02 '21 at 14:12

3 Answers3

2

I did some searching but it seems that the docker CLI currently just does not offer any straight forward way to do this. The closest thing is the idea I proposed in my comment: Build the main image and tag all intermediate images afterwards.

Take this Dockerfile as an example:

FROM alpine AS frontend
RUN sleep 15 && touch /frontend

FROM alpine AS backend
RUN sleep 15 && touch /backend

FROM alpine AS runtime
COPY --from=frontend /frontend /frontend
COPY --from=backend /backend /backend

(the sleeps are only there to make the speedup by caching obvious)

Building this with:

export DOCKER_BUILDKIT=1 # enable buildkit for parallel builds
docker build -t my-project .
docker build -t my-project-backend --target backend .
docker build -t my-project-frontend --target frontend .

will

  1. build the main image runtime by first building all required intermediate images, e.g. frontend and backend, and tag only the main image with my-project
  2. build the target backend tagged as my-project-backend but using the cache from the previous build
  3. same but for backend

Every image here will only be built once - but ultimately this is the very same you already did as stated in your question, just in a different order.

If you really want to be able to do this in a single command you could use docker-compose to build the "multiple images":

version: "3.8"
services:
  my-project:
    image: my-project
    build: .
  backend:
    image: my-project-backend
    build:
      context: .
      target: backend
  frontend:
    image: my-project-frontend
    build:
      context: .
      target: frontend
export DOCKER_BUILDKIT=1 # enable buildkit for parallel builds
export COMPOSE_DOCKER_CLI_BUILD=1 # use docker cli for building
docker-compose build

Here docker-compose will basically run the same docker build commands as above for you.

In both cases though you should be aware that although the cached layers massively speed up the build there is still a new build taking place which will each time:

  • send the build context - i.e. content of the current directory - to the docker daemon
  • download any remote files you ADD to the image and only use the cache if the contents are the same again - which for large files/slow network will be a noticeable slow down.

Another workaround I found in this forum thread was to add a LABEL to the image and use docker image ls --filter to get the image IDs after the build.

But testing this it seems docker image ls won't show intermediate images when using buildkit. Also this approach would required more commands / a dedicated script - which would be again more work than your current approach.

acran
  • 1,795
  • 5
  • 20
0

I don't think there's a way to create a container for two applications, and I don't think that's the right way to do it.

A container docker is created for a single application, and even for logs how to do with two applications? If you want to stop one or restart one ?

I think the right way to do it is docker-compose

and use somethink like:

docker-compose.yml:

version: "3.3"
services:
  my-project-frontend:
    build: "./my-project-frontend/"
    container_name: "front"
    restart: always
    depends_on:
      -  my-project-backend
      
   my-project-backend:
    build: "./my-project-backend/"
    container_name: "back"
    restart: always

And run:

docker-compose up

or

docker-compose build
  • 1
    The question isn't about using the same container for multiple applications but about using [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) to build multiple _images_ most efficiently. – acran Jan 05 '21 at 15:24
  • Once docker-compose build done you will have different images, you will have "only one container" (containers group) only by using docker-compose up – Thomas Saison Jan 05 '21 at 15:33
  • Yes, that's correct, but it will not tag the intermediate images of a multi-stage build - which OP requires to be able to push them. `docker-compose` is focused on a completely different use case which does not apply _here_^^ – acran Jan 05 '21 at 15:39
-2

I think what you are looking for is Docker Compose Here is an example of what you can do using several containers to build a Wordpress app:

version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}