This example uses NodeJS but the same principal could apply to Composer in PHP or Python's Pip.

This creates a "library" image that is not meant to be used directly. The ONBUILD commands will run when someone does FROM this container, not when this container itself is built.

If we named this library file Dockerfile-base (probably easier to maintain this file outside any project directory though) it can be build like so:

$ docker build -t node-project-base - < Dockerfile-base

The individual project's Dockerfile would look like:

FROM node-project-base

The project Dockerfile doesn't need any more code than this. When built, this "child" Dockerfile will:

  1. Make a temporary /build directory and an /app directory where the app will be installed.
  2. Copy the entire project into /build. It does this because of the conditional build statement on line 7, only copy package-lock.json if it exists. Note that this can copy a lot of files at build time so use .dockerignore in your project to tell Docker what files not to copy.
  3. Copy the project ./src directory into /app
  4. Copy package.json into /app
  5. Copy package-lock.json into /app if it exists
  6. Run npm install in /app
  7. Remove the build directory.

The result is an image with the project source code and node_modules installed into the /app dir.

The package-lock.json file can be used to fetch specific versions of packages in order to have a reproducible build. If the package-lock.json file does not exist, NPM will find the latest compatible versions of packages listed in package.json.

The project's Dockerfile is now very clean and if we need to copy additional files or RUN additional commands, only the specific ones for this project need to be added - we have removed NodeJS boilerplate for this project and can re-use it on others.