A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession. [0]
Așadar, un Dockerfile eficient presupune o imagine eficientă. De ce ai vrea o imagine de Docker eficientă? Și ce înseamnă, de fapt, o imagine eficientă?
Conform glosarului oficial,
Docker images are the basis of containers[^1]. An Image is an ordered collection of root filesystem changes and the corresponding execution parameters for use within a container runtime. An image typically contains a union of layered filesystems stacked on top of each other. An image does not have state and it never changes.
Are timpul de build scurt, rezultând în feedback mai frecvent în cadrul SDLC [2];
Este de dimensiuni mici, ceea ce implică transfer de rețea mai rapid (docker push / docker pull), un feedback mai prompt și costuri mai mici. (De regulă, furnizorii de servicii cloud nu sunt prea îngăduitori când vine vorba de costuri.)
În continuare, vom itera ideile de mai sus peste un Dockerfile simplu, dar ineficient.
FROM ubuntu
COPY . /app
RUN apt-get update -yq
RUN apt-get install -yq cowsay
RUN cd /app/ && pip install -r
requirements.txt
CMD ["python", "/app/main.py"]
Niciodată nu folosi Docker tag-ul [4] "latest" (menționat implicit în exemplul de mai sus) - nici în imaginea de bază, nici în cea finală. Nu ai niciun control sau certitudine asupra versiunii imaginii. "Latest" NU înseamnă cea mai recentă și nu e nici immutable;
Pentru imaginea de bază, e important să pornești de la o imagine oficială care să conțină doar ce ai nevoie. În cazul aplicației Python de mai sus, python:3.7.3-stretch e poate cea mai potrivită. (Versiunea minoră de Python este discutabilă);
Copiatul întregului proiect nu e recomandat. Codul se schimbă mult mai frecvent decât configurația. Pentru a profita de Docker build cache [5], acestea trebuie copiate separat;
Înlănțuie pașii de update și install pentru dependințe. De asemenea, nu e nevoie de caching pentru dependințe, se întâmplă deja aceasta via build cache;
Creează un utilizator în interiorul containerului. În mod implicit, utilizatorul e root, dar cum root poate schimba UID și GID-ul unui proces pe sistemele bazate pe Linux, iar Docker mapează UID si GID-urile din container pe sistemul host [6], se poate ajunge la o escaladare de privilegii; [7]
Folosește instrucția WORKDIR în Dockerfile, dacă trebuie să treci dintr-un director în altul (pentru claritate, vezi și Dockerfile Best Practices [8]);
Folosește multi-stage build pentru a rula linters și restul suitei de teste în cadrul procesului de build [9];
Dockerfile-ul final arată în felul următor:
# Multi-stage build using wheel lets us compile on
# the first image,
# create wheel files for all dependencies
# and install them in the second image without installing the compilers.
### Base
FROM python:3.7.3-stretch as base
LABEL maintainer="IDK "
COPY requirements/prod-requirements.txt /tmp/
RUN \
apt-get update -yq && \
apt-get install -yq --no-install-recommends \
cowsay=3.* && \
apt-get -y clean && \
rm -rf /var/lib/apt/lists/ * && \
pip --no-cache-dir install
-r /tmp/prod-requirements.txt
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# fail if error at any stage in the pipe below
RUN echo "this was a multi-stage build..." |
/usr/games/cowsay
COPY src /app
### Test
FROM base as test
COPY requirements/test-requirements.txt /tmp/
RUN pip --no-cache-dir install -r
/tmp/test-requirements.txt
RUN \
pylint --disable=R,C,W /app/hello.py && \
bandit -v -r app && \
radon mi -s app && \
radon cc -s app
### Release
FROM gcr.io/distroless/python3:debug as release
COPY --from=base /app /app
COPY --from=base
/usr/local/lib/python3.7/site-packages
/usr/local/lib/python3.7/site-packages
ENV PYTHONPATH=/usr/local/lib/python3.7/site-packages
WORKDIR /app
USER 1001
CMD [ "hello.py" ]
[0] https://docs.docker.com/engine/reference/builder/
[1] https://docs.docker.com/glossary/
[2] https://en.wikipedia.org/wiki/Systems_development_life_cycle
[3] https://docs.docker.com/engine/reference/commandline/system_prune/
[4] https://docs.docker.com/engine/reference/commandline/tag/
[5] https://thenewstack.io/understanding-the-docker-cache-for-faster-builds/
[6] https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf/
[7] https://en.wikipedia.org/wiki/Privilege_escalation/
[8] https://docs.docker.com/v17.09/engine/userguide/eng-image/dockerfile_best-practices/#workdir
[9] https://docs.docker.com/develop/develop-images/multistage-build/