Docker hat ein neues Produkt: docker agent. Ein CLI-Plugin, ähnlich wie compose damals. Man definiert Agents in einer YAML.
Posts mit Tag "docker"
Ich hab ja die Serie “HTML-Elemente und -Attribute, die kaum jemand kennt, aber doch ganz praktisch sind”. Da stoße ich meistens drauf, wenn ich mal durch die MDN-Doku scrolle. Oder einen Browser-Changelog lese.
Heute habe ich mal durch die Doku von Dockerfiles gescrollt. Hier ein paar Sachen, die man in der Praxis selten sieht, aber dennoch super sind.
Git-Repo als Build-Context
Docker-Build direkt aus einem Git-Repo:
docker build -t mein-image https://github.com/paperless-ngx/paperless-ngx.git#main
Das ist übrigens ein super Argument dafür, Multi-Stage-Dockerfiles zu machen, die ohne Verarbeitung außerhalb der Dockerfile funktionieren. Ich sehe immer noch Dockerfiles, wo einfach nur eine außerhalb des Buildprozesses kompilierte Binary reinkopiert wird. Wenn man alles in Build-Stages hat, kann man das direkt so bauen. Vielleicht jetzt nicht die super-option für die CI-Pipeline, aber um mal schnell ein Image von einem Git-Repo zu bauen, ist das super.
Dementsprechend kann man das auch in einer compose.yaml 1 als Build-Context nehmen:
services:
paperless:
build: https://github.com/paperless-ngx/paperless-ngx.git#main
# ...
COPY
Wo wir gerade bei Multi-Stage-Builds waren: COPY --from kann nicht nur von anderen Stages kopieren, sondern auch aus lokalen und remote-Images. Z. B. empfiehlt composer so, wie man sich die aktuelle Binary holt:
# Specific release
COPY --from=composer/composer:2-bin /composer /usr/bin/composer
COPY kann aber auch --chmod, --chown, womit man direkt noch die Permissions anpassen kann.
Auch interessant für Multi-Stage-Build ist: COPY --link. Das ist ein etwas komplexerer Tipp, deshalb belasse ich es beim Link auf die Doku.
Mich hat aus den Socken gehauen:
# syntax=docker/dockerfile:1
FROM scratch
COPY --parents ./src/**/*.txt /parents/
# Build context:
# ./src/a.txt
# ./src/x/b.txt
# ./src/x/y/c.txt
#
# Output:
# /parents/src/a.txt
# /parents/src/x/b.txt
# /parents/src/x/y/c.txt
Globbing mit Erhaltung der Verzeichnisstruktur!
Kommen wir zu einem weiteren unterschätztem Command:
ADD
ADD kann --chmod, --chown und --link wie auch COPY.
Was aber viele nicht wissen, die Quelle kann auch ein Git-Repo oder eine HTTP-URL sein:
ADD https://example.com/archive.zip /usr/src/things/
ADD git@github.com:user/repo.git /usr/src/things/
Das Git-Repo wird dann geklont. Die URL runtergeladen.
Sicher habt ihr schonmal curl && sha256sum -c && tar -xzf gemacht oder gesehen. Ich jedenfalls schon sehr oft. ADD kann mit --unpack=true auch direkt das Zip-Archiv entpacken. Und noch besser, mit --checksum kann man auch direkt eine Checksum angeben.
ADD --unpack=true \
--checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d \
https://example.com/archive.tar.gz /usr/src/things/
Da spart man sich direkt curl und tar zu installieren.
--unpack ist per Default übrigens true, wenn die Quelle ein lokales Archiv ist. Bei remote-Archiven ist es false.
Ist die Quelle ein Git-Repo, dann kann man mit --checksum die Commit-Id angeben.
Mal gucken, ob ich noch weitere Scahen finde. Hier gibt es noch weitere Tipps auf meinem Blog. Der mit dem Copy-From-Image war schonmal dabei.
Footnotes
-
Das ist der neue Name für die
docker-compose.yml, die auch keinversion-Feld mehr hat. ↩
Docker kann mehrere Build-Contexte. Damit kann man dann shared-Directorys haben, ohne den Build-Context auf den gemeinsamen Root zu legen.
Docker kann beim Bauen linten. Vielleicht ist euch mal eine Warning aufgefallen, wenn gebaut wird. Wenn man z. B. FROM x as y schreibt, gibt es eine Warning, die besagt, dass das as in einem anderen Casing als FROM ist.
Das kann man jetzt zu einem Fehler machen. Dazu entweder docker build mit --check aufrufen oder den Anfang der Dockerfile ein bisschen konfigurieren;
# syntax=docker/dockerfile:1
# check=error=true
Diese Checks gibt es aktuell.
Zu Docker hab ich noch was neues gelernt. Man kann jetzt (mit DOCKER_BUILDKIT=1) auch bei einzelnen RUN-Commands Dinge mounten. Das ist ganz praktisch für Lockfiles oder Dinge, die man nur ein Mal braucht. Dazu gibt es neben Bind-Mounts auch Cahce-Mounts, womit man ein Cache-Verzeichnis in den Build-Container mounten kann. Nimmt natürlich etwas Reproduzierbarkeit, aber in gewissen Fällen (npm, rust) kann man damit echt viel Zeit sparen:
Aus
COPY package.json package-lock.json ./
RUN npm ci --no-audit
COPY ./Cargo.toml ./Cargo.toml
RUN cargo fetch
COPY ./clitool.c /clitool.c
RUN g++ -O3 -o /bin/clitool /clitool.c
Wird:
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \ # mit cache des globalen npm-caches
npm ci --no-audit
RUN --mount=type=bind,source=Cargo.toml,target=Cargo.toml \
cargo fetch
RUN --mount=type=bind,source=clitool.c,target=/clitool.c \
g++ -O3 -o /bin/clitool /clitool.c
Je nach Fall kann man sich damit sogar eine Multi-Stage-Dockerfile ersparen.
TIL docker system df:
# docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 5 1.318GB 858.6MB (65%)
Containers 5 5 3.416kB 0B (0%)
Local Volumes 5 1 442B 354B (80%)
Build Cache 0 0 0B 0B Man kennt ja aus Docker-Multistage-Builds das COPY --from=build, um eine Datei aus einer anderen Stage zu kopieren.
Gerade habe ich beim Docker-Image von composer gesehen, dass das nicht nur beschränkt auf Images im selben Multi-stage-Build ist. Man kann dort jedes Image angeben.
Also kann man statt dem hier:
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
&& php composer-setup.php --install-dir=/usr/local/bin --filename=composer \
&& php -r "unlink('composer-setup.php');" \
&& composer --version
Auch das hier machen und die Binary aus dem offiziellen composer-Image kopieren:
COPY --from=composer /usr/bin/composer /usr/bin/composer
Nachteil dabei ist, dass der Builder dafür das komplette Image runterladen muss. Ist in manchen Situationen vielleicht ganz praktisch.
TIL docker wait.
Letztens hatte ich einen Post zu selten verwendeten Features bei docker-compose. Hier ein guter, aber oberflächlicher Artikel: 6 Docker Compose Best Practices for Dev and Prod
Daraufhin hat sich noch ein weiteres Feature aufgetan: profiles. Beispiel:
version: "3.9"
services:
frontend:
image: frontend
profiles: ["frontend"]
phpmyadmin:
image: phpmyadmin
depends_on:
- db
profiles:
- debug
backend:
image: backend
db:
image: mysql
Here the services
frontendandphpmyadminare assigned to the profilesfrontendanddebugrespectively and as such are only started when their respective profiles are enabled.
In den Kommentaren auf HN gibt es auch noch weitere Tipps.
Heute habe ich gelernt: In docker-compose kann man sich sowas basteln, was ähnlich zu den Init-Containern aus k8s ist. Verwenden tut man dafür die depends_on-Property des Services.
Bisher ging ich davon aus, dass man mit der Property nur festlegen kann, in welcher Reihenfolge die Container gestartet werden. Das bringt einem aber nicht viel, weil die Container üblicherweise unterschiedlich lange brauchen, um zu starten. Das geht mittlerweile besser, nur die Dokumentation erwähnt es (noch) nicht:
Bei den Elementen von depends_on kann man conditions angeben. Dort kann man sagen, in welchem Zustand die Abhängigkeit sein soll, bevor der Container gestartet wird.
services:
db:
image: mariadb:10
ports:
- "3306:3306"
init_db:
image: mariadb:10
command: /init-db.sh
volumes:
- ./init-db.sh:/init-db.sh
env_file:
- credentials.env
depends_on:
db:
condition: service_started
application:
# ...
depends_on:
init_db:
condition: service_completed_successfully
Durch das service_completed_successfully sagt man, dass application erst startet, wenn init_db mit einem 0-Exit-Code beendet ist.
service_started ist offenbar das, was sonst auch immer der Fall war (die Doku nennt es “legacy behaviour”).
Ich hab dann noch ein bisschen Recherche betrieben und rausgefunden:
Da geht noch mehr! Definiert man z. B. die Healthchecks, über die ich mal berichtet habe, kann man den Condition service_healthy angeben. Diesen Status erhält ein Container, wenn er hochgefahren und sich durch den Healthcheck als “gesund” erkannt wurde.
Definiert man das in der Dockerfile oder in der docker-compose.yml, kann man damit sogar ein ./wait-for-it.sh loswerden.
Da hat sich ja echt was getan bei docker-compose.
Heute lernte ich die HEALTHCHECK-Instruktion bei Dockerfiles. Damit kann ein Container einen Befehl definieren, mit dem man rausbekommen kann, ob der Container gerade “Healthy” ist. Erinnert ein bisschen an die Probes aus K8s.
HEALTHCHECK --start-period=10s --timeout=10s \
CMD curl --fail http://localhost:80 || exit 1
Man kann das auch in einer Compose-File definieren[hinweis].