Writing a good Dockerfile for a Perl app
Permalink for this talk:
http://domm.plix.at/talks/2020_cic_good_dockerfile_perlapp
Shout-Out
Farhad Shahbazi sphericalelephant.com for help and hand-holding while dockerizing our Perl apps |
A very short introduction to Dockerfiles
Dockerfile ~ a script to install, setup and run an application and it's dependencies
FROM debian:buster-slim
FROM debian:buster-slim RUN cpan Some::Module
FROM debian:buster-slim RUN cpan Some::Module WORKDIR /usr/src/your-app
FROM debian:buster-slim RUN cpan Some::Module WORKDIR /usr/src/your-app COPY your_script.pl /usr/src/your-app/your_script.pl
FROM debian:buster-slim RUN cpan Some::Module WORKDIR /usr/src/your-app COPY your_script.pl /usr/src/your-app/your_script.pl CMD [ "/usr/src/your-app/your_script.pl" ]
Example App
Net::Matrix::Webhook
As featured in my lightning talk a few minutes ago
https://metacpan.org/pod/Net::Matrix::Webhook
https://github.com/domm/Net-Matrix-Webhook
A IO::Async
http
Server with one endpoint where you can post messages to
Also a matrix.org
bot-user that will forward those messages into a chat room
I use it in combination with App::TimeTracker
~/validad/Accounts-Mono$ tracker start --issue 130 --cat deploy Started working on Accounts-Mono (issue#130, deploy) fix stats via accounts-evtw deployment at 23:01:12 Labels are now: Doing, bug, prio:critical Switched to a new branch '130-fix-stats-via-accounts-evtw-deployment'
A naive Dockerfile
https://hub.docker.com/_/perl/
file: Dockerfile.naive1: FROM perl:5.30.2-buster 2: 3: COPY . /usr/src/myapp 4: 5: WORKDIR /usr/src/myapp 6: 7: RUN cpanm -n --installdeps . 8: 9: CMD [ "perl", "-I/usr/src/myapp/lib","/usr/src/myapp/bin/http2matrix.pl" ] 10:
docker build -t net-matrix-webhook_naive:0.901 -f Dockerfile.naive .
docker build -t net-matrix-webhook_naive:0.901 -f Dockerfile.naive . Sending build context to Docker daemon 7.299MB Step 1/5 : FROM perl:5.30.2-buster ---> de6996f7ef50 Step 2/5 : COPY . /usr/src/myapp ---> 9b86e387cffb Step 3/5 : WORKDIR /usr/src/myapp ---> Running in 3247a83e7842 Removing intermediate container 3247a83e7842 ---> 252c162075d4 Step 4/5 : RUN cpanm -n --installdeps . ---> Running in 130518a3d357 --> Working on . Configuring /usr/src/myapp ... OK ==> Found dependencies: Module::Build, Log::Any::Adapter, Net::Async::Matrix, IO::Async::SSL, IO::Async::Timer::Countdown, Plack::Response, Log::Any, Plack::Request, Net::Async::HTTP::Server::PSGI, Digest::SHA1, IO::Async::Loop, Class::Accessor::Fast --> Working on Module::Build ... 53 distributions installed Removing intermediate container 34f92f22d59e ---> beec8e0646ce Step 5/5 : CMD [ "perl", "-I/usr/src/myapp/lib","/usr/src/myapp/bin/http2matrix.pl" ] ---> Running in ea3ecd0128f0 Removing intermediate container ea3ecd0128f0 ---> 78105a9ab5d1
docker build -t net-matrix-webhook_naive:0.901 -f Dockerfile.naive . Sending build context to Docker daemon 7.299MB Step 1/5 : FROM perl:5.30.2-buster ---> de6996f7ef50 Step 2/5 : COPY . /usr/src/myapp ---> 9b86e387cffb Step 3/5 : WORKDIR /usr/src/myapp ---> Running in 3247a83e7842 Removing intermediate container 3247a83e7842 ---> 252c162075d4 Step 4/5 : RUN cpanm -n --installdeps . ---> Running in 130518a3d357 --> Working on . Configuring /usr/src/myapp ... OK ==> Found dependencies: Module::Build, Log::Any::Adapter, Net::Async::Matrix, IO::Async::SSL, IO::Async::Timer::Countdown, Plack::Response, Log::Any, Plack::Request, Net::Async::HTTP::Server::PSGI, Digest::SHA1, IO::Async::Loop, Class::Accessor::Fast --> Working on Module::Build ... 53 distributions installed Removing intermediate container 34f92f22d59e ---> beec8e0646ce Step 5/5 : CMD [ "perl", "-I/usr/src/myapp/lib","/usr/src/myapp/bin/http2matrix.pl" ] ---> Running in ea3ecd0128f0 Removing intermediate container ea3ecd0128f0 ---> 78105a9ab5d1 Successfully built 78105a9ab5d1 Successfully tagged net-matrix-webhook_naive:0.901
docker run --init --rm -p8765:8765 \ -e MATRIX_HOME_SERVER="matrix.example.net" -e MATRIX_USER="r2d2" -e MATRIX_PASSWORD="hunter2" \ -e MATRIX_ROOM='#test:example.net' net-matrix-webhook_naive:0.901
docker run --init --rm -p8765:8765 \ -e MATRIX_HOME_SERVER="matrix.example.net" -e MATRIX_USER="r2d2" -e MATRIX_PASSWORD="hunter2" \ -e MATRIX_ROOM='#test:example.net' net-matrix-webhook_naive:0.901
curl http://localhost:8765/?message=Hello%20Perl%207
What is a "Good Dockerfile"?
Fast to build
Fast to rebuild after a change
Small image
Naive Dockerfile performance?
Time to build
time docker build -t net-matrix-webhook_naive:0.901 -f Dockerfile.naive .
time docker build -t net-matrix-webhook_naive:0.901 -f Dockerfile.naive . real 1m9,022s user 0m1,375s sys 0m0,285s
Not too bad
Waiting a bit over 1m for a build during CI is acceptable
Size
docker images net-matrix-webhook_naive:0.901 --format "{{.Size}}"
docker images net-matrix-webhook_naive:0.901 --format "{{.Size}}" 892MB
Nearly 1 GB, this is not good.
Cloud storage costs...
Network costs of sending the image to the registry...
Time costs of sending the image to the registry and downloading it later...
Time to rebuild
echo "# change" >> lib/Net/Matrix/Webhook.pm
echo "# change" >> lib/Net/Matrix/Webhook.pm time docker build -t net-matrix-webhook_naive:0.901 -f Dockerfile.naive .
echo "# change" >> lib/Net/Matrix/Webhook.pm time docker build -t net-matrix-webhook_naive:0.901 -f Dockerfile.naive . real 1m9,656s user 0m1,324s sys 0m0,307s
Same as original build time.
This is very bad!
Disgression: Layer caching
Each line in a Dockerfile creates a new layer.
A layer is only recreated if something changes inside the layer.
For most docker instructions, this means that layer creation is skipped if the line in the Dockerfile did not change
But we did not change any lines in the Dockerfile, so why did the caching of the layers not work?
Because COPY
is special!
And COPY .
is evil!!
COPY
will copy files from your workdir into the container.
If a file that has been copied has changed, this will invalidate the layer cache from this line on.
file: Dockerfile.naive 1: FROM perl:5.30.2-buster 2: 3: COPY . /usr/src/myapp 4: 5: WORKDIR /usr/src/myapp 6: 7: RUN cpanm -n --installdeps . 8: 9: CMD [ "perl", "-I/usr/src/myapp/lib","/usr/src/myapp/bin/http2matrix.pl" ]
If anything changes in this repo, the COPY .
in line 3 invalidates the RUN cpanm
in line 7, thus we have to reinstall all deps every time we change something in our code.
Even worse, as we're using COPY .
(COPY EVERYTHING), any change in the repo (eg to the Dockerfile) will invalidate the following layers.
A less naive Dockerfile
file: Dockerfile.less_naive 1: FROM perl:5.30.2-buster 2: 3: WORKDIR /usr/src/myapp 4: 5: COPY cpanfile /usr/src/myapp/cpanfile 6: RUN cpanm -n --installdeps . 7: 8: COPY bin /usr/src/myapp/bin 9: COPY lib /usr/src/myapp/lib 10: 11: CMD [ "perl", "-I/usr/src/myapp/lib","/usr/src/myapp/bin/http2matrix.pl" ]
file: Dockerfile.less_naive 1: FROM perl:5.30.2-buster 2: 3: WORKDIR /usr/src/myapp 4: 5: COPY cpanfile /usr/src/myapp/cpanfile 6: RUN cpanm -n --installdeps . 7: 8: COPY bin /usr/src/myapp/bin 9: COPY lib /usr/src/myapp/lib 10: 11: CMD [ "perl", "-I/usr/src/myapp/lib","/usr/src/myapp/bin/http2matrix.pl" ]
just COPY the cpanfile to install deps
file: Dockerfile.less_naive 1: FROM perl:5.30.2-buster 2: 3: WORKDIR /usr/src/myapp 4: 5: COPY cpanfile /usr/src/myapp/cpanfile 6: RUN cpanm -n --installdeps . 7: 8: COPY bin /usr/src/myapp/bin 9: COPY lib /usr/src/myapp/lib 10: 11: CMD [ "perl", "-I/usr/src/myapp/lib","/usr/src/myapp/bin/http2matrix.pl" ]
later explicitly COPY bin and lib
So a change to your code will not trigger a re-install of your dependencies
Stats?
Time to build: 66s
Size: 885MB
Time to rebuild: 2s (down from 70s)
Nice!
Size went down from 892MB to 885MB
So we saved 7MB!
Why?
Because COPY .
also copies .git
and other crap.
NEVER USE COPY .
!!!
Disgression: don't run app as root inside Docker
file: Dockerfile.from_perl_image 1: FROM perl:5.30.2-buster 2: 3: COPY cpanfile cpanfile 4: RUN cpanm -n --installdeps . 5: 6: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 7: USER http2matrix 8: WORKDIR /home/http2matrix 9: 10: COPY bin bin 11: COPY lib lib 12: 13: CMD [ "perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
file: Dockerfile.from_perl_image 1: FROM perl:5.30.2-buster 2: 3: COPY cpanfile cpanfile 4: RUN cpanm -n --installdeps . 5: 6: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 7: USER http2matrix 8: WORKDIR /home/http2matrix 9: 10: COPY bin bin 11: COPY lib lib 12: 13: CMD [ "perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
Time to build: 70s
Size: 885MB
Time to rebuild: 2s
no changes here...
Make image smaller
We learned that the prepacked Perl images are rather big
So we build our own
file: Dockerfile.from_debian_image 1: FROM debian:buster-slim 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: COPY cpanfile cpanfile 17: RUN cpanm -n --installdeps . 18: 19: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 20: USER http2matrix 21: WORKDIR /home/http2matrix 22: 23: COPY bin bin 24: COPY lib lib 25: 26: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
file: Dockerfile.from_debian_image 1: FROM debian:buster-slim 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: COPY cpanfile cpanfile 17: RUN cpanm -n --installdeps . 18: 19: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 20: USER http2matrix 21: WORKDIR /home/http2matrix 22: 23: COPY bin bin 24: COPY lib lib 25: 26: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
file: Dockerfile.from_debian_image 1: FROM debian:buster-slim 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: COPY cpanfile cpanfile 17: RUN cpanm -n --installdeps . 18: 19: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 20: USER http2matrix 21: WORKDIR /home/http2matrix 22: 23: COPY bin bin 24: COPY lib lib 25: 26: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
file: Dockerfile.from_debian_image 1: FROM debian:buster-slim 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: COPY cpanfile cpanfile 17: RUN cpanm -n --installdeps . 18: 19: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 20: USER http2matrix 21: WORKDIR /home/http2matrix 22: 23: COPY bin bin 24: COPY lib lib 25: 26: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
one statement, thus one layer
file: Dockerfile.from_debian_image 1: FROM debian:buster-slim 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: COPY cpanfile cpanfile 17: RUN cpanm -n --installdeps . 18: 19: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 20: USER http2matrix 21: WORKDIR /home/http2matrix 22: 23: COPY bin bin 24: COPY lib lib 25: 26: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
clean up after yourself
The rest stays unchanged
Results?
Time to build: 131s (up from 66s)
Size: 341MB (down from 885MB)
Time to rebuild: 2s
Less then half the size, but double the inital build time.
But the most of the time you will be able to reuse all the cached layers
Also, if you update the dependencies via cpanfile, we can still re-use some layers:
file: Dockerfile.from_debian_image 1: FROM debian:buster-slim 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: COPY cpanfile cpanfile 17: RUN cpanm -n --installdeps . 18: 19: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 20: USER http2matrix 21: WORKDIR /home/http2matrix 22: 23: COPY bin bin 24: COPY lib lib 25: 26: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
cpanfile changed, but apt-get is still cached
Time to rebuild after dep change: 72s
Which is slightly slower than a full rebuild using the less optimized Dockerfiles, and half the time of a full build
For smaller images, use multi-stage builds
https://docs.docker.com/develop/develop-images/dockerfile_best-practices
You can have several FROM
lines in your Dockerfile
And copy files from one stage into the next
E.g. you can have a build
stage containing a compiler and other build tools
And a run
stage that copies the built artifacts from the build
stage, without all the build deps
file: Dockerfile.from_debian_multi1: FROM debian:buster-slim as build 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: RUN adduser --disabled-password --disabled-login --gecos "perl user" --home /home/perl perl 17: USER perl 18: WORKDIR /home/perl 19: 20: ONBUILD COPY cpanfile cpanfile 21: ONBUILD RUN cpanm -n --installdeps -l /home/perl/local . 22: 23: FROM debian:buster-slim 24: RUN apt-get update \ 25: && apt-get install -y \ 26: apt-transport-https \ 27: ca-certificates \ 28: perl \ 29: liblocal-lib-perl \ 30: && apt-get clean \ 31: && rm -rf /var/lib/apt/lists 32: 33: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 34: USER http2matrix 35: WORKDIR /home/http2matrix 36: 37: COPY --from=build /home/perl/local /home/http2matrix/local 38: COPY bin bin 39: COPY lib lib 40: 41: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","-Mlocal::lib=/home/http2matrix/local","/home/http2matrix/bin/http2matrix.pl" ] 42:
file: Dockerfile.from_debian_multi 1: FROM debian:buster-slim as build 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: RUN adduser --disabled-password --disabled-login --gecos "perl user" --home /home/perl perl 17: USER perl 18: WORKDIR /home/perl 19: 20: ONBUILD COPY cpanfile cpanfile 21: ONBUILD RUN cpanm -n --installdeps -l /home/perl/local .
'as build' gives this stage a name
file: Dockerfile.from_debian_multi 1: FROM debian:buster-slim as build 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: RUN adduser --disabled-password --disabled-login --gecos "perl user" --home /home/perl perl 17: USER perl 18: WORKDIR /home/perl 19: 20: ONBUILD COPY cpanfile cpanfile 21: ONBUILD RUN cpanm -n --installdeps -l /home/perl/local .
install build tools, compilers etc (take a lot of place)
file: Dockerfile.from_debian_multi 1: FROM debian:buster-slim as build 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: perl \ 10: cpanminus \ 11: libssl-dev \ 12: zlib1g-dev \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: RUN adduser --disabled-password --disabled-login --gecos "perl user" --home /home/perl perl 17: USER perl 18: WORKDIR /home/perl 19: 20: ONBUILD COPY cpanfile cpanfile 21: ONBUILD RUN cpanm -n --installdeps -l /home/perl/local .
ONBUILD will be run in a later stage
Defined here, but executed when another stage uses this stage
Very helpful if you want to base several other Dockerfiles from the same build-image
file: Dockerfile.from_debian_multi 23: FROM debian:buster-slim 24: RUN apt-get update \ 25: && apt-get install -y \ 26: apt-transport-https \ 27: ca-certificates \ 28: perl \ 29: liblocal-lib-perl \ 30: && apt-get clean \ 31: && rm -rf /var/lib/apt/lists 32: 33: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 34: USER http2matrix 35: WORKDIR /home/http2matrix 36: 37: COPY --from=build /home/perl/local /home/http2matrix/local 38: COPY bin bin 39: COPY lib lib 40: 41: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","-Mlocal::lib=/home/http2matrix/local","/home/http2matrix/bin/http2matrix.pl" ]
file: Dockerfile.from_debian_multi 23: FROM debian:buster-slim 24: RUN apt-get update \ 25: && apt-get install -y \ 26: apt-transport-https \ 27: ca-certificates \ 28: perl \ 29: liblocal-lib-perl \ 30: && apt-get clean \ 31: && rm -rf /var/lib/apt/lists 32: 33: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 34: USER http2matrix 35: WORKDIR /home/http2matrix 36: 37: COPY --from=build /home/perl/local /home/http2matrix/local 38: COPY bin bin 39: COPY lib lib 40: 41: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","-Mlocal::lib=/home/http2matrix/local","/home/http2matrix/bin/http2matrix.pl" ]
again start from debian:buster-slim
file: Dockerfile.from_debian_multi 23: FROM debian:buster-slim 24: RUN apt-get update \ 25: && apt-get install -y \ 26: apt-transport-https \ 27: ca-certificates \ 28: perl \ 29: liblocal-lib-perl \ 30: && apt-get clean \ 31: && rm -rf /var/lib/apt/lists 32: 33: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 34: USER http2matrix 35: WORKDIR /home/http2matrix 36: 37: COPY --from=build /home/perl/local /home/http2matrix/local 38: COPY bin bin 39: COPY lib lib 40: 41: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","-Mlocal::lib=/home/http2matrix/local","/home/http2matrix/bin/http2matrix.pl" ]
less debian deps, no build tools
file: Dockerfile.from_debian_multi 23: FROM debian:buster-slim 24: RUN apt-get update \ 25: && apt-get install -y \ 26: apt-transport-https \ 27: ca-certificates \ 28: perl \ 29: liblocal-lib-perl \ 30: && apt-get clean \ 31: && rm -rf /var/lib/apt/lists 32: 33: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 34: USER http2matrix 35: WORKDIR /home/http2matrix 36: 37: COPY --from=build /home/perl/local /home/http2matrix/local 38: COPY bin bin 39: COPY lib lib 40: 41: CMD [ "/usr/bin/perl", "-I/home/http2matrix/lib","-Mlocal::lib=/home/http2matrix/local","/home/http2matrix/bin/http2matrix.pl" ]
COPY only the local-lib dir from build-stage
Results?
Time to build: 165s (up from 131s, 66s)
Size: 143MB (down from 341MB, 885MB)
Time to rebuild: 2s
Time to rebuild after dep change: 70s
Quite nice!
Build everything by ourselves, with stages
file: Dockerfile.perlbuild1: FROM debian:buster-slim as build 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: libssl-dev \ 10: zlib1g-dev \ 11: perl \ 12: cpanminus \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: RUN cpanm Perl::Build 17: RUN perl-build -j4 5.30.2 /opt/perl-5.30.2 18: RUN curl -L https://cpanmin.us | /opt/perl-5.30.2/bin/perl - App::cpanminus 19: 20: COPY cpanfile cpanfile 21: RUN /opt/perl-5.30.2/bin/cpanm -n --installdeps . 22: 23: FROM debian:buster-slim 24: 25: COPY --from=build /opt/perl-5.30.2 /opt/perl-5.30.2 26: COPY --from=build /usr/lib/x86_64-linux-gnu/libssl* /usr/lib/x86_64-linux-gnu/ 27: COPY --from=build /usr/lib/x86_64-linux-gnu/libcrypto* /usr/lib/x86_64-linux-gnu/ 28: COPY --from=build /usr/lib/x86_64-linux-gnu/libz.* /usr/lib/x86_64-linux-gnu/ 29: 30: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 31: USER http2matrix 32: WORKDIR /home/http2matrix 33: 34: COPY bin bin 35: COPY lib lib 36: 37: CMD [ "/opt/perl-5.30.2/bin/perl","-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ] 38:
file: Dockerfile.perlbuild 1: FROM debian:buster-slim as build 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: libssl-dev \ 10: zlib1g-dev \ 11: perl \ 12: cpanminus \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: RUN cpanm Perl::Build 17: RUN perl-build -j4 5.30.2 /opt/perl-5.30.2 18: RUN curl -L https://cpanmin.us | /opt/perl-5.30.2/bin/perl - App::cpanminus 19: 20: COPY cpanfile cpanfile 21: RUN /opt/perl-5.30.2/bin/cpanm -n --installdeps .
building a custom Perl via Perl::Build
file: Dockerfile.perlbuild 1: FROM debian:buster-slim as build 2: 3: RUN apt-get update \ 4: && apt-get install -y \ 5: apt-transport-https \ 6: build-essential \ 7: ca-certificates \ 8: curl \ 9: libssl-dev \ 10: zlib1g-dev \ 11: perl \ 12: cpanminus \ 13: && apt-get clean \ 14: && rm -rf /var/lib/apt/lists 15: 16: RUN cpanm Perl::Build 17: RUN perl-build -j4 5.30.2 /opt/perl-5.30.2 18: RUN curl -L https://cpanmin.us | /opt/perl-5.30.2/bin/perl - App::cpanminus 19: 20: COPY cpanfile cpanfile 21: RUN /opt/perl-5.30.2/bin/cpanm -n --installdeps .
just install deps into the fresh perl, no need for local::lib
file: Dockerfile.perlbuild 23: FROM debian:buster-slim 24: 25: COPY --from=build /opt/perl-5.30.2 /opt/perl-5.30.2 26: COPY --from=build /usr/lib/x86_64-linux-gnu/libssl* /usr/lib/x86_64-linux-gnu/ 27: COPY --from=build /usr/lib/x86_64-linux-gnu/libcrypto* /usr/lib/x86_64-linux-gnu/ 28: COPY --from=build /usr/lib/x86_64-linux-gnu/libz.* /usr/lib/x86_64-linux-gnu/ 29: 30: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 31: USER http2matrix 32: WORKDIR /home/http2matrix 33: 34: COPY bin bin 35: COPY lib lib 36: 37: CMD [ "/opt/perl-5.30.2/bin/perl","-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
file: Dockerfile.perlbuild 23: FROM debian:buster-slim 24: 25: COPY --from=build /opt/perl-5.30.2 /opt/perl-5.30.2 26: COPY --from=build /usr/lib/x86_64-linux-gnu/libssl* /usr/lib/x86_64-linux-gnu/ 27: COPY --from=build /usr/lib/x86_64-linux-gnu/libcrypto* /usr/lib/x86_64-linux-gnu/ 28: COPY --from=build /usr/lib/x86_64-linux-gnu/libz.* /usr/lib/x86_64-linux-gnu/ 29: 30: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 31: USER http2matrix 32: WORKDIR /home/http2matrix 33: 34: COPY bin bin 35: COPY lib lib 36: 37: CMD [ "/opt/perl-5.30.2/bin/perl","-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
copy the whole perl
file: Dockerfile.perlbuild 23: FROM debian:buster-slim 24: 25: COPY --from=build /opt/perl-5.30.2 /opt/perl-5.30.2 26: COPY --from=build /usr/lib/x86_64-linux-gnu/libssl* /usr/lib/x86_64-linux-gnu/ 27: COPY --from=build /usr/lib/x86_64-linux-gnu/libcrypto* /usr/lib/x86_64-linux-gnu/ 28: COPY --from=build /usr/lib/x86_64-linux-gnu/libz.* /usr/lib/x86_64-linux-gnu/ 29: 30: RUN adduser --disabled-password --disabled-login --gecos "http2matrix user" --home /home/http2matrix http2matrix 31: USER http2matrix 32: WORKDIR /home/http2matrix 33: 34: COPY bin bin 35: COPY lib lib 36: 37: CMD [ "/opt/perl-5.30.2/bin/perl","-I/home/http2matrix/lib","/home/http2matrix/bin/http2matrix.pl" ]
and some shared librarys we need
Results?
Time to build: 338s (up from 165s, 131s, 66s)
Size: 147MB (same as 143MB, down from 341MB, 885MB)
Time to rebuild: 3s
Time to rebuild after dep change: 75s (vs 70s)
Probably not worth the effort...
Summary
Never use COPY . .
Think about the ordering of your Dockerfile statements to optimize layer caching and build time
Use multi-stage builds to get image sizes down
Questions?
Bonus
dive
https://github.com/wagoodman/dive
dive net-matrix-webhook_naive:0.901
dive net-matrix-webhook_perlbuild:0.901