Timm Heuss
April 2024
block-beta
columns 2
app1["My App 1"] app2["My App 2"]
d1["Dependencies 1"] d2["Dependencies 2"]
ul1["Guest Userland"] ul2["Guest Userland"]
k1["Guest Kernel"] k2["Guest Kernel"]
i1["Guest Infrastructure"] i2["Guest Infrastructure"]
vm["Virtual Machine"]:2
k["Host Kernel"]:2
i["Host Infrastructure"]:2
- Virtualised hardware
- OS kernel and drivers
block-beta
columns 2
app1["My App 1"] app2["My App 2"]
d1["Dependencies 1"] d2["Dependencies 2"]
block:b:2
Base["Base image"]
space
end
Docker["Docker Engine"]:2
k["Host Kernel"]:2
i["Host Infrastructure"]:2
- Containers are not running an OS kernel
- Layered, caching file system
flowchart LR
Dockerfile --> build --> image["Docker Image"] --> run --> container["Docker Container"]
image --> push --> registry[Docker Registry]
registry --> pull --> image
compose[compose.yml] --> services
services --> service
compose --> image
service --> image
service --> up --> container
- Reduce the size of our shipment, because
- less storage needed
- better transferring times
- lower the attack surface
- Reduce build time
- only fast builds will be executed frequently
- Make builds reproducible
- nobody has time to debug
Building and running images
Dockerfile
FROM ubuntu # -> apt
FROM alpine # -> apk
FROM scratch # -> no package manager
FROM python # -> You cannot tell without looking into it
FROM python:3.13-rc-alpine3.18 # -> alpine -> apk
FROM python:3.13-rc-bookworm # -> ???
- "bookworm" is a Debian version
- Debian uses
apt
FROM python:3.13-rc-bookworm # -> apt
RUN apt update && apt install -y git
- no pinned version for
git
- keeps the apt cache in the layer
- potentially installs unwanted packages
๐
RUN apt-get update && \
apt-get -y install --no-install-recommends \
git=1.2.25.1 \
&& rm -rf /var/lib/apt/lists/*
- pinned version for git
- removes the cache within the same layer before commit
- does not install additional software
๐
๐
FROM ubuntu
RUN apt-get update && apt-get install -y git apache2
WORKDIR /var/www/html/
RUN git clone https://github.com/oscarmorrison/md-page
RUN git clone https://github.com/artslob/fallout-svg
COPY . ./
RUN echo '<script src="md-page/md-page.js"></script><noscript>' > 'index.html' && \
cat "readme.md" >> 'index.html'
EXPOSE 80
CMD ["apache2ctl", "-D", "FOREGROUND"]
docker build . -t hello
docker images
docker run --publish 80:80 hello
docker ps
docker stop / kill
251MB image
๐ค apt caches, git repos, tons of binaries
COPY . ./
Invalidates cache on every change in the folder.
๐
COPY readme.md ./
Invalidates cache only when readme.md
is changed.
๐
FROM ubuntu as base
FROM base as build
[...]
FROM base as runtime
COPY --from=build /workdir/md-page/md-page.js ./md-page/md-page.js
COPY --from=build /workdir/fallout-svg/vault-boy.svg ./fallout-svg/vault-boy.svg
COPY --from=build /workdir/index.html ./
[...]
Distinguish between build and runtime dependencies
FROM ubuntu as base
FROM base as build
[...]
FROM base as runtime
[...]
docker build . # builds until target "runtime"
docker build . --target build # builds until target "build"
FROM base as build
RUN apt-get update && apt-get install -y git
Manual installation, waste of time ๐
from alpine/git as build
Wit the right base image we download an installed git ๐
FROM base as build
RUN apt-get update && apt-get install -y apache2
โฌ๏ธ Better โฌ๏ธ
from httpd
WORKDIR /usr/local/apache2/htdocs/
FROM alpine/git AS git1
[...]
FROM alpine/git AS git2
[...]
FROM ubuntu as build
[...]
FROM httpd
[...]
hello latest b7733e03e9ac 10 minutes ago 178MB
178 MB (-30%)
FROM joseluisq/static-web-server
WORKDIR /public
scenario | image size (MB) | build time (s) |
---|---|---|
starting point | 258 | 170 |
multi-stage, same base images | 178 (-30%) | 2 (-98%) |
multi-stage, more suitable base images | 8 (-97%) | 1 (-99%) |
reduced build times, reduced waiting times, less resource consumption, lower transferring times, reduced attack surface, ...
FROM httpd:2.4.59 AS httpd
FROM alpine/git:2.43.0 AS git
WORKDIR /workdir/md-page
RUN git clone https://github.com/oscarmorrison/md-page . && \
git reset 36eef73bbbd35124269f5a8fea3b5117cd7a91a3
WORKDIR /workdir/fallout-svg
RUN git clone https://github.com/artslob/fallout-svg . && \
git reset d1dad0950073bdef8cac463f8a87246f45af0ca0
Reminder: We're not simulating hardware in containers
docker pull nxginx
Docker pulls nxginx
with the right CPU architecture for the host, with a fallback to amd64
FROM --platform=linux/amd64 node:18-slim
This is useful e.g. for Playwright which has runtime dependencies that are not available for all platforms.
Orchestrating multi-container deployments
compose.yaml (preferred)
compose.yml
docker-compose.override.yml
docker-compose.override.yaml
docker-compose.yaml
docker-compose.yml
services:
service:
build: .
ports:
- "80:80"
version: 3.2
services:
app:
image: 'docker-spring-boot-postgres:latest'
build: .
depends_on:
- db
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/compose-postgres
- SPRING_DATASOURCE_USERNAME=compose-postgres
- SPRING_DATASOURCE_PASSWORD=compose-postgres
db:
image: 'postgres:13.1-alpine'
environment:
- POSTGRES_PASSWORD=compose-postgres
- Essential for multiple services
- easy execution
- easy configuration
- the right abstraction for developers to describe their application
old
docker-compose
new ๐
docker compose
version: 3.2 # <- optional
services:
app:
[...]
The [version property] is [...] for backward compatibility. It is only informative.
https://github.com/compose-spec/compose-spec/blob/master/04-version-and-name.md
services:
service:
deploy:
replicas: 5
More on that in my talk: Polyglot | scalable | observable news analysis
x-build: &build
x-bake:
platforms:
- linux/amd64
- linux/arm64
services:
keyword-matcher-go:
image: ghcr.io/heussd/nats-news-analysis/keyword-matcher-go:latest
build:
<<: *build
context: keyword-matcher-go/.
services:
service:
build: .
ports:
- "80:80"
volumes:
- ./folder:/usr/local/apache2/htdocs/folder/
Make a folder on your host system available inside the container
services:
service:
build: .
ports:
- "80:80"
volumes:
- cache:/usr/local/[...]
volumes:
cache:
Persist a folder across container executions
COPY go.mod go.sum .
RUN go mod download -x
COPY
just for the package manager- Package manager cache is not be persisted
๐
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
- No
COPY
just for the package manager - Package manager cache is persisted across builds
๐
Expert Docker building
docker-bake.json
docker-bake.override.json
docker-bake.hcl (preferred)
docker-bake.override.hcl
variable "HOME" {
default = null
}
group "default" {
targets = ["all"]
}
target "all" {
platforms = [
"linux/amd64", \
"linux/ppc64le"
],
labels = {
"org.opencontainers.image.source" = "https://github.com/username/myapp"
"com.docker.image.source.entrypoint" = "Dockerfile"
}
}
docker buildx bake # build multiple images with all labels
... or just use environment variables, I'm not your boss.
docker build \
--secret id=mytoken,src=$HOME/.aws/credentials \
.
access inside container:
TOKEN=$(cat /run/secrets/mytoken)
variable "HOME" {
default = null
}
target "default" {
secret = [
"id=mytoken,src=${HOME}/.aws/credentials"
]
}
access inside container:
TOKEN=$(cat /run/secrets/mytoken)
services:
service:
secrets:
- mytoken
secrets:
mytoken:
file: ./my_secret.txt
access inside container:
TOKEN=$(cat /run/secrets/mytoken)
latest
is just a tag, no automationlatest
has no common meaning on Docker Hub
docker pull ubuntu:latest # <- Pulls latest stable LTS
docker pull swaggerapi/swagger-ui:latest # <- Pulls latest nightly
RUN
in Dockerfile
FROM ubuntu
RUN whalesay "OMG" # runs code during image build
run
on CLI
docker run ubuntu # runs a container
Dockerfile statement | build phase | run phase | purpose |
---|---|---|---|
RUN | โ | Execute an command, commit result into image | |
ENTRYPOINT | โ | Specify what to do when container is executed | |
CMD | โ | Augment ENTRYPOINT with addition parameters |
ARGS
are environment variables during the build phase.- Runtime arguments can be specified using
CMD
(Dockerfile
) orcommands
(docker-compose). - Docker commands are command line parameters to the Docker binary (such as
docker images
).
FROM git as build # <- This is referenced as "target",
# not as a "stage"
WORKDIR /workdir
COPY readme.md ./
docker build . --target build
Respected during image build
ENV MSG="Hello world"
RUN echo $MSG
Not respected during image build:
service:
environment:
- MSG="Hello World"
... Mount | Purpose | Compose | Docker CLI |
---|---|---|---|
Bind mount | Access external files / folders | volumes |
docker run -v |
Bind mount | ... during run | RUN --mount=type=bind |
|
Volume mount | Access internal volumes | volumes |
docker run -v |
Secret mount | Access secrets | secrets |
docker secret / docker build --secrets |
Cache mount | Cache some paths for runs | RUN --mount=type=cache |
block-beta
columns 4
space space space app
space space space ide["IDE"]
space space space dev["Dev tools"]
db["Additional containers"] space space interpreter["Code Interpreter"]
Docker["Docker Engine"]:2 space Win["Host OS / Applications"]
k["Host Kernel"]:4
i["Host Infrastructure"]:4
style ide fill:red
style dev fill:red
style interpreter fill:red
Reproducibility challenges with dev tools and code interpreter.
block-beta
columns 4
space app space space
space dev["Dev tools"] space space
db["Additional containers"] interpreter["Code Interpreter"] space IDE
Docker["Docker Engine"]:2 space Win["Host OS / Applications"]
k["Host Kernel"]:4
i["Host Infrastructure"]:4
IDE --> interpreter
style dev fill:green
style interpreter fill:green
Machine-readable reproducibility for dev tools and code interpreter.
block-beta
columns 5
space app space:3
space dev["Dev tools"] space:3
db["Additional containers"] interpreter["Code Interpreter"] cs["Codespaces / Coder"] space Browser
Docker["Docker Engine"]:2 space:2 Win["Host OS / Applications"]
Cloud:3 space k["Host Kernel"]
style dev fill:green
style interpreter fill:green
style cs fill:blue
style Browser fill:blue
Browser --> cs
- prebuilt base image such as
mcr.microsoft.com/devcontainers/base:ubuntu
- local
Dockerfile
- local
compose.yml
These cannot be mixed, so compose.yml
it is if the other's dont work.
Dockerfile | compose.yml | devcontainer.json | |
---|---|---|---|
Runtime environment | Base image | Base image, Dockerfile | Base image, Dockerfile, compose.yml |
Support services / databases | services | ||
Build dependencies | Install in layer | ||
Additional dev / convenience tooling | Features | ||
IDE settings and plugins | for supported IDEs |
{
"build": {
"dockerfile": "../Dockerfile",
"target": "dev"
},
"features": {
"ghcr.io/shinepukur/devcontainer-features/vale:1": {},
"ghcr.io/devcontainers/features/git:1": {},
},
"customizations": {
"vscode": {
"settings": {
"dotfiles.repository": "https://github.com/heussd/dotfiles",
"dotfiles.targetPath": "~/.dotfiles",
"dotfiles.installCommand": ".install.sh",
},
"extensions": [
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-german",
"DavidAnson.vscode-markdownlint",
"bierner.markdown-mermaid"
]
}
},
"postStartCommand": "node /app/bin/reveal-md.js . --watch"
}
build
: Base image / Dockerfile / compose.ymlfeatures
: Additional development-only toolingsettings
: IDE settings, dotfilesextensions
: IDE addonspostStartCommand
: What to do after devcontainer has started
Exploring a docker image and layer contents
https://github.com/wagoodman/dive
Dockerfile linter, validate inline bash
https://github.com/hadolint/hadolint
top
-like interface for container metrics
https://github.com/bcicen/ctop
Terminal UI for both docker and docker-compose