Import stages
import:
- from: <image name or external_image>
  stage: <stage name>
  before: <install  setup>
  after: <install  setup>
  add: <absolute path>
  to: <absolute path>
  owner: <owner>
  group: <group>
  includePaths:
  - <relative path or glob>
  excludePaths:
  - <relative path or glob>

The size of the final image can grow dramatically due to the assembly tools and source files eating up space. These files are generally not needed in the final image. To avoid this, the Docker community suggests installing tools, building, and removing irrelevant files in one step:

RUN “download-source && cmd && cmd2 && remove-source”

You can do the same in werf — just specify the relevant instructions for some user stage. Below is an example of specifying the shell assembly instructions for the install stage:

shell:
  install:
  - "download-source"
  - "cmd"
  - "cmd2"
  - "remove-source"

However, this method does not support caching. Thus, a build toolkit will be installed all over again.

Another way is to use a multi-stage build, a feature supported in Docker since version 17.05:

FROM node:latest AS storefront
WORKDIR /app
COPY react-app .
RUN npm install
RUN npm run build

FROM maven:latest AS appserver
WORKDIR /app
COPY . .
RUN mvn package

FROM java:8-jdk-alpine
COPY --from=storefront /app/react-app/build/ /static
COPY --from=appserver /app/target/AtSea-0.0.1-SNAPSHOT.jar /app/AtSea.jar

The point of this approach is to describe several auxiliary images and selectively copy artifacts from one image to another, leaving all the unnecessary data outside the final image.

werf offers the same approach.

Why doesn’t werf use multi-stage assembly?

  • Historically, imports came much earlier than the Docker multi-stage mechanism, and
  • werf allows for greater flexibility when working with auxiliary images.

Import configuration

Importing resources from the images must be described in the import directive in the destination image in the image config section. import is an array of records, where each record must contain the following:

  • from: <image name>: source image; the name of the image copy files from. Both imports from images of the current project and from external images in the format image_name:tag or image_name@digest are supported.
  • add: <absolute path>: source path; the absolute path to the file or directory in the source image to copy from.
  • to: <absolute path>: destination path; the absolute path in the destination image. If absent, the destination path defaults to the source path (as specified by the add directive).
  • before: <install || setup> or after: <install || setup>: destination image stage; the stage to import files. Currently, only install and setup stages are supported.

An example of the import directive:

import:
  - from: application-assets
    add: /app/public/assets
    to: /var/www/site/assets
    after: install
  - from: node:20-alpine
    add: /usr/share/nginx/html
    to: /var/www/site/prebuilt
    after: setup

As with the git mappings configuration, include and exclude file and directory masks are supported (includePaths: [] and excludePaths: [], respectively). Masks must be specified relative to the source path (as in the add parameter). You can also specify an owner and a group for the imported resources, owner: <owner> and group: <group>. This behavior is similar to the one used when adding code from Git repositories, and you can read more about it in the git directive section.

Note that the path of imported resources and the path specified in git mappings must not overlap.

Destination path rules

What ends up at the destination path depends on whether the source path is a directory or a file.

If add is a directory, its contents are merged into to. Files already present in to are kept, and files with the same name are overwritten. This is why several imports may target the same directory.

If add is a file, the outcome depends on the destination image:

  • if to does not exist, the file is created at exactly that path — add: /app/config.yaml with to: /etc/app.yaml produces /etc/app.yaml;
  • if to already exists as a directory, the file is placed inside it — the same import with to: /etc produces /etc/config.yaml.

werf creates only the parent directory of to, so whether to itself exists is decided by the destination image — pin the full file path when the difference matters.

A trailing slash in to (anything other than to: /) is a configuration error and fails the build: write to: /usr/sbin, not to: /usr/sbin/.