Red Green Repeat Adventures of a Spec Driven Junkie

How to Conditionally Copy a File in Dockerfile

Been playing with Docker images a lot lately… like a lot:

My list of Docker images

One thing I want to do when building a Docker image from a Dockerfile: only copy over a file when it is there, otherwise, skip the file.

Motivation

I want to be able to copy over certain files when building the Docker image if they exist in the current folder. Otherwise, the Docker image can take a different course of action, like generate a basic version of the optional file.

Requirements

To follow along, please install Docker.

It also helps to have a Dockerfile to test it out. If you don’t have one, the following Dockerfile file contents will suffice:

FROM ruby:2.5.1-slim

RUN mkdir /app
WORKDIR /app

All this does is start the ruby project and makes the working directory /app. We will append the COPY command will at the end of this Dockerfile.

Short Answer

Specify any optional files for the COPY command with * along with one real file, the COPY command needs to copy at least one file over, even if there is more than one optional file to copy with the * option.

Example

To have the COPY command in the Dockerfile copy over another_file, which may or may not be there, write the command as:

COPY Dockerfile another_file* .

This will ensure the COPY operation is successful. The Dockerfile becomes:

FROM ruby:2.5.1-slim

RUN mkdir /app
WORKDIR /app

COPY Dockerfile another_file* .
Step 6/9 : COPY Dockerfile another_file* .
 ---> 434302cde3

It is vital to append * to the optional files, otherwise, COPY will throw an error:

Step 6/9 : COPY Dockerfile another_file .
COPY failed: stat /var/lib/docker/tmp/docker-builder068314739/another_file: no such file or directory

Details

I stumbled upon this answer when perusing the Docker forum. It didn’t look like anything special and I tried just COPY another_file* with no success.

What struck me was the answer had two arguments, one with the * and without. With no other solutions in sight, I tried the command again, but exactly as typed.

Specifying a file that exists with another that may exist is exactly the behavior I want. I got it working by running: COPY Dockerfile another_file*.

I found the easiest way to always this is to always copy over the Dockerfile, which must exist. So, if I want to copy a Gemfile for Ruby projects or package.json for node projects, I would specify in the Dockerfile the command to be:

COPY Dockerfile Gemfile* package.json* .

Then building the image would result in:

Step 6/9 : COPY Dockerfile Gemfile* package.json* .
 ---> 9f2d0497c5a2

If Dockerfile is not specifed, then COPY throwns an error.

Step 6/9 : COPY Gemfile* package.json* .
COPY failed: no source files were specified

The Docker image build process stops.

Conclusion

Just by successfully copying over one file for Docker’s COPY command, the Docker image build process will continue, even if only one of multiple files specified are successfully copied.