121

I have a Dockerfile for PHP like this :

FROM php:7-fpm
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update && \
    apt-get install -y git libicu-dev libmagickwand-dev libmcrypt-dev libcurl3-dev jpegoptim
RUN pecl install imagick && \
    docker-php-ext-enable imagick

RUN docker-php-ext-install intl
RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-install opcache
RUN docker-php-ext-install mcrypt
RUN docker-php-ext-install curl
RUN docker-php-ext-install zip

And I'd like to create another Dockerfile, based on the first one, but with some PHP extensions added (for dev purpose) : Xdebug and other stuffs.

Can I create a "dev" Dockerfile that extends my main Dockerfile (without rewrite it) ?

Sylvain
  • 2,742
  • 5
  • 21
  • 34
  • 4
    You can certainly base your new dockerfile on the image you made from the first one – Jimmy Apr 01 '16 at 17:42
  • 5
    Can the Dockerfile extend another file, as in not have to build an intermediate image? – Frank Robert Anderson May 25 '18 at 15:44
  • 4
    @FrankRobertAnderson In a huge discussion about adding an `INCLUDE` directive in Dockerfiles, this commenter [suggests using make](https://github.com/moby/moby/issues/735#issuecomment-37273719) to compose a Dockerfile from parts. – asciimo Jul 11 '18 at 18:30

5 Answers5

100

Using multi-stage build is definitely one part of the answer here.

docker-compose v3.4 target being the second and last.

Here is a example to have 2 containers (1 normal & 1 w/ xdebug installed) living together :

Dockerfile

FROM php:7-fpm AS php_base 
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update && \
    apt-get install -y git libicu-dev libmagickwand-dev libmcrypt-dev libcurl3-dev jpegoptim
RUN pecl install imagick && \
    docker-php-ext-enable imagick

RUN docker-php-ext-install intl
RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-install opcache
RUN docker-php-ext-install mcrypt
RUN docker-php-ext-install curl
RUN docker-php-ext-install zip

FROM php_base AS php_test

RUN pecl install xdebug 
RUN docker-php-ext-enable xdebug

docker-compose.yml

version: '3.4'

services:
  php:
    build:
      context: ./
      target: php_base

  php_test:
    build:
      context: ./
      target: php_test
  
# ...
Cethy
  • 1,552
  • 2
  • 15
  • 22
  • 3
    Yes! [This](https://docs.docker.com/develop/develop-images/multistage-build/#use-a-previous-stage-as-a-new-stage) seem to solve the problem I have. – Josef Sábl Mar 18 '20 at 12:19
  • So, each `FROM` separates from other stage? – ssi-anik May 12 '21 at 18:25
  • @ssi-anik not sure if it answers your question but `FROM` *initialize* a new build stage. more in the doc : https://docs.docker.com/engine/reference/builder/#from – Cethy May 13 '21 at 09:43
  • Yes, that helps. FROM starts a new stage Thanks. @Cethy – ssi-anik May 13 '21 at 09:59
  • 1
    What if the two stages are in different dockerfiles? How can docker-compose know to build one stage before the other? – Simon Tran Sep 11 '22 at 17:19
59

If you don't want to tag your first Dockerfile in order to use it in a FROM instruction in your next Dockerfile, and you are using Docker 20.10+, you can also do this:

# syntax = edrevo/dockerfile-plus

INCLUDE+ Dockerfile.base

RUN whatever

The INCLUDE+ instruction gets imported by the first line in the Dockerfile. You can read more about the dockerfile-plus at https://github.com/edrevo/dockerfile-plus

edrevo
  • 1,147
  • 13
  • 17
  • Thanks for creating this, easy to use and sorely missing from Docker, imo. Does your solution play seamlessly with the Docker caching mechanisms, for example when modifying the included file(s) or the including file itself? – Reinier Torenbeek May 11 '21 at 19:07
  • 1
    It does! This is basically just replacing the INCLUDE+ directive with whatever the included file has, so the Docker builder (buildkit) will receive a completely expanded and standard Dockerfile, which means this plays well with build layer caching. – edrevo May 14 '21 at 07:23
  • Multi-Stage is good but we can not reuse part of code in other stages elsewhere. Docker compose can build all multi-stage layers, but still leaves this issue. ONLY solution available right now is this one- to have this frontend in builkit with INCLUDE+ Great job for posting this solution. I hope it will be actively maintained or one day pulled upstream. – ashish Jul 27 '21 at 14:53
  • 1
    This is so great. It seems like such a simple and obvious way to layer Dockerfiles. Thanks for this! – Joel Mellon Oct 09 '21 at 02:20
  • I don't get this to work, am I doing anything wrong? I've added the comment line as the first line of my Dockerfile and then add INCLUDE+ ../Dockerfile (I'm in a test directory), but I get: failed to solve with frontend dockerfile.v0: failed to solve with frontend gateway.v0: rpc error: code = Unknown desc = failed to create LLB definition: dockerfile parse error line 5: unknown instruction: INCLUDE+ – Guus Nov 12 '21 at 14:15
  • 1
    @Guus Please set the environment variables `DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1` as outlined in the README (otherwise, with docker 20.10.12 it does not work) . In short: On linux, following works for me: `DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker build -f Dockerfile.test -t demo .` – koppor Jan 05 '22 at 06:44
  • @edrevo Would it be possible to have Dockerfile B include Dockerfile A, and then have Dockerfile C include Dockerfile B? Currently that doesn't work with `INCLUDE+`. – neu242 Feb 24 '22 at 09:24
  • 1
    It seems it's not compatible with dockerignore so not that great a solution sadly – Nathan Appere Jul 01 '22 at 12:29
  • @NathanAppere what do you mean by not compatible? The INCLUDE+ simply replaces the directive with the original content of the included file, so the .gitignore applies as expected. Checked with [docker engine `20.10.18`, docker client `20.10.21`] – Cililing Dec 06 '22 at 15:12
15

That is exactly what your FROM php:7-fpm is doing: extending the Dockerfile from the php image (with 7-fpm tag) with the contents of your Dockerfile.

So after building an image from your Dockerfile:

docker build -t my-php-base-image .

You can extend that by creating a new Dockerfile that starts with:

FROM my-php-base-image
larsks
  • 277,717
  • 41
  • 399
  • 399
  • 63
    What if you have a docker-compose setup where you want to base one container on another on dockerfile level? Imaging is not an option here if multiple versions of the environment should be run on one maschine. – Niksac Jun 07 '17 at 18:03
  • I'm not sure I understand your question. I would suggest posting it as an actual question, and include an example of what you would like to accomplish. That will probably make it easier for people to suggest solutions. – larsks Jun 08 '17 at 19:46
  • 73
    I think this answer is not good since the question was how to import a `dockerfile` and not a built image. I think the basic question is "how to save duplicate code on multiple similar Dockerfiles"? – WebQube Jul 02 '18 at 09:11
  • I think that is exactly what this question answers. Layering on top of existing image is *exactly* how you extend the `Dockerfile` used in the underlying image. – larsks Jul 02 '18 at 11:53
  • @Niksac There's a controversial [pull request](https://github.com/moby/moby/pull/12749) for an `INCLUDE` statement with optional intermediary image building. – asciimo Jul 11 '18 at 18:43
  • 12
    That;s nothing. i want to have a debug version of dockerfile that extends the primary dockerfile adding gdb . With this I would have to always do docker build on primary and the extended... – Martin Kosicky Jul 13 '18 at 08:42
  • 2
    @Niksac did you find a solution? This is exactly what I need right now – Shardj Nov 06 '18 at 16:40
  • 3
    @larsks For example, I have the exact same Dockerfile for two different Ubuntu releases, except that the FROM line at the beginning is different -- one is FROM the ubuntu:trusty image, the other FROM the ubuntu:bionic image. I'd love a way to include everything after the FROM line as some kind of Dockerfile include, rather than maintaining two copies of the same Dockerfile code in two different files. – CivFan Mar 27 '19 at 19:01
  • 4
    The answer even uses word "exactly". That's not exactly "exactly". – Gherman Mar 26 '20 at 10:42
  • 3
    I'd say this answer is wrong because it conflates source code with built artefact. `FROM php:7-fpm` builds on top of a docker image, not a Docker file. Specifically the symbol `php:7-fpm` references the name given to a built image in a docker registry not another (Dockerfile) file on the local file system. Whether or not it fitted the OP's needs, it doesn't answer the question as written. – Philip Couling Sep 23 '20 at 10:23
  • @PhilipCouling Exactly. It's a fine solution to most cases of extending a docker _image_, but it doesn't fit all uses. As long as you only want to do something to the final image, and not something variable in the middle, this is the simple method. – Auspex Oct 12 '21 at 12:33
  • this answer does not cover the scenario when you have two Dockerfiles locally and want to exend one of them. Scneario: I want to extend the Dockerfile from some public repo without modifying the original Dockerfile so I can automate git pulls withtout having git complain about changes – sp4ke Jul 23 '22 at 09:47
0

Sand is a small open source tool I wrote to address this need (and many others) by allowing you to write your dockerfiles in a python-like syntax that enables you to share code between them.

Given the following directory structure:

my-monorepo/
│
├── tweet-service/
|   ├── src/
|   ├── ...
│   └── Sandfile
│
├── home-timeline/
|   ├── src/
|   ├── ...
│   └── Sandfile
│
└── Sandfile

You can write your Sandfiles like this:

# ./my-monorepo/Sandfile
from sand import *

def MyService(name):
    From("ubuntu", "20.04")
    Run("apt-get install python3")
    Copy(Src="src", Dst="/app")
    Entrypoint(f"python3 /app/{name}.py")

Sand("tweet-service")
Sand("home-timeline")
# ./my-monorepo/tweet-service/Sandfile
from sand import *

MyService("tweet-service") # Defined in ../Sandfile
# ./my-monorepo/home-timeline/Sandfile
from sand import *

MyService("home-timeline") # Defined in ../Sandfile

This allows you to share code between your Dockerfiles, and keep them DRY.

This is similar to the way add_subdirectory works in CMake

gkpln3
  • 1,317
  • 10
  • 24
0

Hello folk's to the needs of factorization on Dockerfile I developed this buildkit custom syntax frontend plugin: devthefuture/dockerfile-x

just one line at the top of file # syntax = devthefuture/dockerfile-x and that's it !

# syntax = devthefuture/dockerfile-x

FROM ./base/dockerfile

COPY --from=./build/dockerfile#build-stage /app /app

INCLUDE ./other/dockerfile

this was inspired by @edrevo works

DevTheJo
  • 2,179
  • 2
  • 21
  • 25