Writing a good Dockerfile for a Perl app

Perl Conference in the Cloud Cyberspace || Vienna 2020-07-25
Thomas Klausner http://domm.plix.at domm AT plix.at

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.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" ] 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_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 . 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.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 . 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