I made a Docker container which is fairly large. When I commit the container to create an image, the image is about 7.8 GB big. But when I export
the container (not save
the image!) to a tarball and re-import it, the image is only 3 GB big. Of course the history is lost, but this OK for me, since the image is "done" in my opinion and ready for deployment.
How can I flatten an image/container without exporting it to the disk and importing it again? And: Is it a wise idea to do that or am I missing some important point?
Develop the Dockerfile in logically separated blocks, but compact it in the final version. Don't create files if you don't have to — use streams and pipes as much as possible. Remove unnecessary files, such as cache files, or don't use the cache in the first place.
You can rebuild the image from the base image without using cached layers by using the --no-cache option. New layers were constructed and used. The docker build runs both commands this time, which comes with an all-or-nothing approach.
It will layers that contain #LAYEREMOVE# in their created_by attribute; so if you put RUN something #LAYEREMOVE# in a Dockerfile, the resulting layer will be removed.
Now that Docker has released the multi-stage builds in 17.05, you can reformat your build to look like this:
FROM buildimage as build # your existing build steps here FROM scratch COPY --from=build / / CMD ["/your/start/script"]
The result will be your build environment layers are cached on the build server, but only a flattened copy will exist in the resulting image that you tag and push.
Note, you would typically reformulate this to have a complex build environment and only copy over a few directories. Here's an example with Go to make a single binary image from source code and a single build command without installing Go on the host and compiling outside of docker:
$ cat Dockerfile ARG GOLANG_VER=1.8 FROM golang:${GOLANG_VER} as builder WORKDIR /go/src/app COPY . . RUN go-wrapper download RUN go-wrapper install FROM scratch COPY --from=builder /go/bin/app /app CMD ["/app"]
The go file is a simple hello world:
$ cat hello.go package main import "fmt" func main() { fmt.Printf("Hello, world.\n") }
The build creates both environments, the build environment and the scratch one, and then tags the scratch one:
$ docker build -t test-multi-hello . Sending build context to Docker daemon 4.096kB Step 1/9 : ARG GOLANG_VER=1.8 ---> Step 2/9 : FROM golang:${GOLANG_VER} as builder ---> a0c61f0b0796 Step 3/9 : WORKDIR /go/src/app ---> Using cache ---> af5177aae437 Step 4/9 : COPY . . ---> Using cache ---> 976490d44468 Step 5/9 : RUN go-wrapper download ---> Using cache ---> e31ac3ce83c3 Step 6/9 : RUN go-wrapper install ---> Using cache ---> 2630f482fe78 Step 7/9 : FROM scratch ---> Step 8/9 : COPY --from=builder /go/bin/app /app ---> Using cache ---> 5645db256412 Step 9/9 : CMD /app ---> Using cache ---> 8d428d6f7113 Successfully built 8d428d6f7113 Successfully tagged test-multi-hello:latest
Looking at the images, only the single binary is in the image being shipped, while the build environment is over 700MB:
$ docker images | grep 2630f482fe78 <none> <none> 2630f482fe78 6 days ago 700MB $ docker images | grep 8d428d6f7113 test-multi-hello latest 8d428d6f7113 6 days ago 1.56MB
And yes, it runs:
$ docker run --rm test-multi-hello Hello, world.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With