Reputation: 10226
I want to build a docker image, which requires both a Neo4j database and Node.js to run.
My first approach was to declare a base image for my image, containing Neo4j. The reference docs do not define "base image" in any helpful manner:
Base image: An image that has no parent is a base image
from which I read that I may only have a base image if that image has no base image itself.
But what is a base image? Does it mean, if I declare neo4j/neo4j in a FROM directive, that when my image is run the neo database will automatically run and be available within the container on port 7474?
Reading the Docker reference I see:
FROM can appear multiple times within a single Dockerfile in order to create multiple images. Simply make a note of the last image ID output by the commit before each new FROM command.
Do I want to create multiple images? It would seem what I want is to have a single image that contains the contents of other images e.g. neo4j and node.js.
I've found no directive to declare dependencies in the reference manual. Are there no dependencies like in RPM where in order to run my image the calling context must first install the images it needs?
Upvotes: 274
Views: 294608
Reputation: 1323065
As of May 2017, multiple FROM
s can be used in a single Dockerfile.
See "Builder pattern vs. Multi-stage builds in Docker" (by Alex Ellis) and PR 31257 by Tõnis Tiigi.
The general syntax involves adding
FROM
additional times within your Dockerfile - whichever is the lastFROM
statement is the final base image. To copy artifacts and outputs from intermediate images useCOPY --from=<base_image_number>
.
FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
The result would be two images, one for building, one with just the resulting app (much, much smaller)
REPOSITORY TAG IMAGE ID CREATED SIZE
multi latest bcbbf69a9b59 6 minutes ago 10.3MB
golang 1.7.3 ef15416724f6 4 months ago 672MB
what is a base image?
A set of files, plus EXPOSE
'd ports, ENTRYPOINT
and CMD
.
You can add files and build a new image based on that base image, with a new Dockerfile
starting with a FROM
directive: the image mentioned after FROM
is "the base image" for your new image.
does it mean that if I declare
neo4j/neo4j
in aFROM
directive, that when my image is run the neo database will automatically run and be available within the container on port 7474?
Only if you don't overwrite CMD
and ENTRYPOINT
.
But the image in itself is enough: you would use a FROM neo4j/neo4j
if you had to add files related to neo4j
for your particular usage of neo4j
.
2018: With the introduction of the --target
option in docker build
, you gain even more control over multi-stage builds.
This feature enables you to select which FROM
statement in your Dockerfile you wish to build, allowing for more modular and efficient Docker images. This is especially useful in scenarios where you might want to:
Build Only the Dependencies: Create an image that only contains the dependencies of your project. This can be useful for caching purposes or for environments where you only need to run tests or static analysis tools.
Separate Build and Runtime Environments: Compile or build your application in a full-featured build environment but create a smaller, more secure image for deployment that only includes the runtime environment and the compiled application.
Create Images for Different Environments: Have different stages for development, testing, and production environments, each tailored with the specific tools and configurations needed for those environments.
--target
Given a Dockerfile with multiple stages named builder
, tester
, and deployer
, you can build up to the tester
stage using the --target
option like so:
docker build --target tester -t myapp-test .
This command tells Docker to stop building after the tester
stage has been completed, thus creating an image that includes everything from the base image up to the tester
stage, but excluding anything from deployer
stage and beyond.
--target
Usage# Builder stage
FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/example/project/
# Assume app.go exists and has a function
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# Tester stage
FROM builder as tester
COPY . .
RUN go test ./...
# Deployer stage
FROM alpine:latest as deployer
COPY --from=builder /go/src/github.com/example/project/app /app
CMD ["/app"]
Using the --target
option with this Dockerfile allows for flexibility in building images tailored for specific steps of the development lifecycle.
As illustrated in "Building a multi-stage Dockerfile with --target flag builds all stages instead of just the specified one", this works well with BuildKit, which is now (2023+) the default builder.
From that page, you have Igor Kulebyakin's answer:
If one wants to make sure that the current target stage is force re-built even if it has already been cached without rebuilding the previous dependent stages, once can use the docker build
--no-cache-filter
flag.An example, given you have a multi-stage Dockerfile with a 'test' stage, would be:
docker build --no-cache-filter test --target test --tag your-image-name:version .
Upvotes: 250
Reputation: 13207
Let me summarize my understanding of the question and the answer, hoping that it will be useful to others.
Question: Let’s say I have three images, apple, banana and orange. Can I have a Dockerfile that has FROM apple
, FROM banana
and FROM orange
that will tell docker to magically merge all three applications into a single image (containing the three individual applications) which I could call smoothie?
Answer: No, you can't. If you do that, you will end up with four images, the three fruit images you pulled, plus the new image based on the last FROM
image. If, for example, FROM orange
was the last statement in the Dockerfile without anything added, the smoothie image would just be a clone of the orange image.
A typical docker image will contain almost everything the application needs to run (leaving out the kernel) which usually means that they’re built from a base image for their chosen operating system and a particular version or distribution.
Merging images successfully without considering all possible distributions, file systems, libraries and applications, is not something Docker, understandably, wants to do. Instead, developers are expected to embrace the microservices paradigm, running multiple containers that talk to each other as needed.
One possible use case for image merging would be to mix and match Linux distributions with our desired applications, for example, Ubuntu and Node.js. This is not the solution:
FROM ubuntu
FROM node
If we don’t want to stick with the Linux distribution chosen by our application image, we can start with our chosen distribution and use the package manager to install the applications instead, e.g.
FROM ubuntu
RUN apt-get update &&\
apt-get install package1 &&\
apt-get install package2
But you probably knew that already. Often times there isn’t a snap or package available in the chosen distribution, or it’s not the desired version, or it doesn't work well in a docker container out of the box, which was the motivation for wanting to use an image. I’m just confirming that, as far as I know, the only option is to do it the long way, if you really want to follow a monolithic approach.
In the case of Node.js for example, you might want to manually install the latest version, since apt
provides an ancient one, and snap
does not come with the Ubuntu image. For neo4j we might have to download the package and manually add it to the image, according to the documentation and the license.
One strategy, if size does not matter, is to start with the base image that would be hardest to install manually, and add the rest on top.
There is also the option to use multiple FROM
statements and manually copy stuff between build stages or into your final one. In other words, you can manually merge images, if you know what you're doing. As per the documentation:
Optionally a name can be given to a new build stage by adding
AS name
to theFROM
instruction. The name can be used in subsequentFROM
andCOPY --from=<name>
instructions to refer to the image built in this stage.
Personally, I’d only be comfortable using this merge approach with my own images or by following documentation from the application vendor, but it’s there if you need it or you're just feeling lucky.
A better application of this approach though, would be when we actually do want to use a temporary container from a different image, for building or doing something and discard it after copying the desired output.
I wanted a lean image with gpgv
only, and based on this Unix & Linux answer, I installed the whole gpg
with yum
and then copied only the binaries required, to the final image:
FROM docker.io/photon:latest AS builder
RUN yum install gnupg -y
FROM docker.io/photon:latest
COPY --from=builder /usr/bin/gpgv /usr/bin/
COPY --from=builder /usr/lib/libgcrypt.so.20 /usr/lib/libgpg-error.so.0 /usr/lib/
The rest of the Dockerfile
continues as usual.
Upvotes: 108
Reputation: 1
The first answer is too complex, historic, and uninformative for my tastes.
It's actually rather simple. Docker provides for a functionality called multi-stage builds the basic idea here is to,
Let's start with the first. Very often with something like Debian you'll see.
RUN apt-get update \
&& apt-get dist-upgrade \
&& apt-get install <whatever> \
&& apt-get clean
We can explain all of this in terms of the above. The above command is chained together so it represents a single change with no intermediate Images required. If it was written like this,
RUN apt-get update ;
RUN apt-get dist-upgrade;
RUN apt-get install <whatever>;
RUN apt-get clean;
It would result in 3 more temporary intermediate Images. Having it reduced to one image, there is one remaining problem: apt-get clean
doesn't clean up artifacts used in the install. If a Debian maintainer includes in his install a script that modifies the system that modification will also be present in the final solution (see something like pepperflashplugin-nonfree
for an example of that).
By using a multi-stage build you get all the benefits of a single changed action, but it will require you to manually allowlist and copy over files that were introduced in the temporary image using the COPY --from
syntax documented here. Moreover, it's a great solution where there is no alternative (like an apt-get clean
), and you would otherwise have lots of un-needed files in your final image.
See also
Upvotes: 33
Reputation: 2225
Here is probably one of the most fundamental use cases of using multiple FROM
s, aka, multi stage builds.
I want want one dockerfile, and I want to change one word and depending on what I set that word to, I get different images depending on whether I want to run, Dev or Publish the application!
Run - I just want to run the app
Dev - I want to edit the code and run the app
Publish - Run the app in production
Lets suppose we're working in the dotnet environment. Heres one single Dockerfile. Without multi stage build, there would be multiple files (builder pattern)
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/runtime:5.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["ConsoleApp1/ConsoleApp1.csproj", "ConsoleApp1/"]
RUN dotnet restore "ConsoleApp1/ConsoleApp1.csproj"
COPY . .
WORKDIR "/src/ConsoleApp1"
RUN dotnet build "ConsoleApp1.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "ConsoleApp1.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConsoleApp1.dll"]
Want to run the app? Leave FROM base AS final
as it currently is in the dockerfile above.
Want to dev the source code in the container? Change the same line to FROM build AS final
Want to release into prod? Change the same line to FROM publish AS final
Upvotes: 8