Every build in this course has been done by BuildKit without you noticing. In this lesson you take control: cache mounts that spare you from reinstalling npm on every build, secrets that leave no trace in any layer, a cache shared between your machine and CI, images that work on amd64 and arm64 at the same time, and a single command to build the whole Aurora Libros platform.
Contents
- What BuildKit is and how it differs from the classic builder
- The graph of a multi-stage build
- Enabling it, verifying it and reading its output
- Buildx and builders
- Mounts in
RUN:type=cache type=bindandtype=tmpfstype=secret: credentials that leave no tracetype=ssh: cloning private repositories- Shared remote cache
- Multi-architecture: QEMU and manifest lists
TARGETPLATFORMand cross-compilation- Outputs with
--output,--loadand--push docker buildx bake
- What BuildKit is and how it differs from the classic builder
The classic builder ran the Dockerfile line by line, in strict order, creating one intermediate container per instruction. BuildKit replaces that with an engine that first analyzes the whole file and builds a dependency graph.
| Aspect | Classic builder | BuildKit |
|---|---|---|
| Execution | Sequential, instruction by instruction | Dependency graph: only what is needed |
| Independent stages | One after another | In parallel |
| Cache | Per layer, in a linear chain | By content; survives reordering |
| Context | Sent whole at the start | Transferred on demand |
| Secrets | Impossible without leaking them | --mount=type=secret, no trace |
| Output | A flat log at the end | Live progress, per step and with timings |
| External cache | No | --cache-from / --cache-to with registries |
| Multi-architecture | One docker build per platform |
One command, several platforms |
The most useful difference day to day: if a stage contributes nothing to the final image, BuildKit does not even run it. That is why the tests stage from lesson 05-04 does not run when you build production.
- The graph of a multi-stage build
flowchart LR CTX["Context<br/>(on demand)"] --> D1["dependencies:<br/>apk add build-base"] CTX --> D2["development:<br/>full npm ci"] D1 --> D3["dependencies:<br/>npm ci --omit=dev"] D2 --> T["tests:<br/>npm test"] D3 --> P["production:<br/>COPY --from=dependencies"] CTX --> P P --> IMG["Final image"] T -.->|"only with --target tests"| X["does not enter the graph<br/>of the final image"]
dependencies and development do not depend on each other: BuildKit runs them at the same time. And tests hangs off development, but nothing in the final image depends on tests, so it is pruned from the graph.
- Enabling it, verifying it and reading its output
Since Docker 23, BuildKit is the default builder on Linux, macOS and Windows.
github.com/docker/buildx v0.19.3
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.42kB doneThe numbered #n lines are BuildKit's signature; the old builder showed Step 1/12 : FROM .... If for some specific reason you need to go back, DOCKER_BUILDKIT=0 docker build ..., but treat that as a diagnostic step, not an option.
To debug a build, the progress mode changes everything:
#12 [dependencies 4/4] RUN npm ci --omit=dev && npm cache clean --force
#12 3.412 added 64 packages in 3s
#12 4.108 npm warn using --force Recommended protections disabled.
#12 DONE 4.3s--progress=plain prints each command's full output with relative timestamps, instead of the interactive view that collapses lines. It is the first thing to turn on when a build fails and you cannot see why.
- Buildx and builders
Buildx is the modern build client. A builder is the BuildKit instance that does the work, and not all of them have the same capabilities.
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
default * docker running linux/amd64, linux/386
default default running| Driver | Where it runs | Multi-architecture | Remote cache | Outputs |
|---|---|---|---|---|
docker (default) |
Inside the daemon | No | inline only |
Local image only |
docker-container |
In a container of its own | Yes | All | All (--output) |
kubernetes |
Pods in a cluster | Yes | All | All |
remote |
An external BuildKit instance | Yes | All | All |
Everything that follows requires docker-container:
docker buildx create --name aurora --driver docker-container --use --bootstrap
docker buildx inspect aurora | grep -E 'Name|Status|Platforms'That builder is an ordinary container (buildx_buildkit_aurora0) with its own cache, independent of the daemon's. You manage it with docker buildx use/stop/rm, and clean it with docker buildx prune --filter until=168h.
- Mounts in
RUN: type=cache
RUN: type=cacheA cache mount is a directory that persists between builds and is not part of any layer. It is the solution to the most common waste of all: reinstalling entire dependency trees because one line of package.json changed.
# syntax=docker/dockerfile:1
FROM node:22-alpine AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
npm ci --omit=devdocker buildx build --target dependencies -t aurora-api:deps ./api # first time
# change a version in package.json and repeat
time docker buildx build --target dependencies -t aurora-api:deps ./apiFirst build (cold cache): 42.7s
After changing package.json, without a cache mount: 39.1s
After changing package.json, with a cache mount: 6.8sFrom 39 seconds to 7. The npm cache has not been downloaded again: npm found the packages in /root/.npm and only resolved the tree. And because the mount is not incorporated into the layer, the final image does not grow by a single byte.
| Parameter | Values | Meaning |
|---|---|---|
target |
A path | Where it is mounted inside the RUN |
id |
Text | Cache identifier; share it between Dockerfiles |
sharing |
shared (default), locked, private |
What happens if two builds use it at once |
mode |
0755… |
Directory permissions |
uid/gid |
Numbers | Owner, useful if the RUN is not root |
sharing=locked is the right value for package managers that do not tolerate concurrent writes (npm, apt, pip). The usual cache directories: /root/.npm (npm), /var/cache/apt and /var/lib/apt/lists (apt), /root/.cache/pip (pip), /go/pkg/mod (Go), /root/.m2 (Maven).
Warning. A cache mount is local to the builder. It does not travel with the image or between machines; to share it between your laptop and CI you use the remote cache from section 9.
type=bind and type=tmpfs
type=bind and type=tmpfstype=bind mounts files from the context (or from another stage) without copying them into a layer, and type=tmpfs gives you an in-memory directory for temporary work:
RUN --mount=type=bind,source=package-lock.json,target=/tmp/lock.json \
node -e "console.log(require('/tmp/lock.json').lockfileVersion)"
# From another stage, without dragging its layers along
RUN --mount=type=bind,from=dependencies,source=/app/node_modules,target=/deps du -sh /deps
RUN --mount=type=tmpfs,target=/tmp/work \
tar xzf /source.tar.gz -C /tmp/work && cp /tmp/work/binary /usr/local/bin/They are for reading or verifying something during the build without it becoming part of the image. Unpacking into tmpfs is fast and guarantees that the intermediate files never reach any layer.
type=secret: credentials that leave no trace
type=secret: credentials that leave no traceThis is the section that solves the problem demonstrated in lessons 05-03 and 05-04. Let's compare the two ways of passing a private npm token.
# ❌ UNSAFE: the ARG stays in the metadata forever
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && \
npm ci && rm .npmrc# ✅ CORRECT: the secret is mounted, used and gone
RUN --mount=type=secret,id=npm_token \
--mount=type=cache,target=/root/.npm,sharing=locked \
NPM_TOKEN="$(cat /run/secrets/npm_token)" \
npm ci --omit=devecho "npm_tok_a91f3c" > /tmp/npm_token.txt
docker buildx build --secret id=npm_token,src=/tmp/npm_token.txt \
--load -t aurora-api:secure ./api
# Does the token show up anywhere?
docker history --no-trunc aurora-api:secure | grep -c 'npm_tok_a91f3c'
docker image inspect aurora-api:secure --format '{{json .Config.Env}}' | grep -c 'npm_tok'
docker save aurora-api:secure | strings | grep -c 'npm_tok_a91f3c'Zero matches across all three checks. Compare that with the same experiment using ARG, where docker history handed back the token in the clear. The mechanism: BuildKit mounts the file at /run/secrets/<id> as a tmpfs that exists only for the duration of that RUN; there is no layer, no metadata, no trace.
The secret can also come from an environment variable, which fits CI secret managers better:
export NPM_TOKEN=npm_tok_a91f3c
docker buildx build --secret id=npm_token,env=NPM_TOKEN --load -t aurora-api:secure ./api
type=ssh: cloning private repositories
type=ssh: cloning private repositoriesWhen a dependency lives in a private Git repository, the instinct is to copy a private key into the build. Never do that: type=ssh forwards your SSH agent without the key ever entering the build.
RUN --mount=type=ssh \
mkdir -p ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts && \
npm ci --omit=devssh-add -l >/dev/null || ssh-add ~/.ssh/id_ed25519
docker buildx build --ssh default --load -t aurora-api:private ./apiThe key never leaves your agent: BuildKit exposes a temporary socket inside the RUN and the signing operations happen on your machine. When the instruction ends, the socket disappears.
- Shared remote cache
A local cache only serves whoever generated it. In CI, every job starts on a clean machine and rebuilds everything from scratch. A remote cache solves that: it is published to a registry and any builder can reuse it.
docker buildx build \
--cache-to type=registry,ref=auroralibros/aurora-api:buildcache,mode=max \
--cache-from type=registry,ref=auroralibros/aurora-api:buildcache \
-t auroralibros/aurora-api:1.3.0 --push ./api| Backend | Where it lives | Advantage | Drawback |
|---|---|---|---|
registry |
In your registry, as one more tag | Shared across machines and with CI | Needs write permission on the registry |
inline |
Inside the image itself | Zero configuration; works with the docker driver |
Caches only the last stage (mode=min) |
gha |
GitHub Actions cache | Built in, no extra registry | Limited to GitHub and a 10 GB quota |
local |
A directory on disk | Fast and network-free | Not shared between machines |
s3 / azblob |
Object storage | Scalable and cheap | Credential configuration |
mode=max stores the cache for every stage, intermediate ones included; mode=min (the default) stores only the final image's layers. For multi-stage builds, mode=max is what really saves time, at the cost of more space in the registry.
# Clean machine: reuses the published cache
docker buildx build --cache-from type=registry,ref=auroralibros/aurora-api:buildcache \
-t auroralibros/aurora-api:1.3.0 --load ./api 2>&1 | grep -c CACHEDNine steps resolved from the cache on a machine that had never built this image. That is the piece that will make the pipeline in lesson 06-02 take seconds instead of minutes.
- Multi-architecture: QEMU and manifest lists
Apple Silicon laptops are arm64, and so is a good share of cloud servers (Graviton, Ampere), while most CI is still amd64. An image built only for amd64 either fails or runs painfully slowly under emulation on arm64.
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx inspect aurora --bootstrap | grep Platformsbinfmt_misc is the kernel feature that associates an interpreter with binaries from another architecture; that privileged container registers the corresponding QEMU emulators (and yes, the --privileged there is justified: it registers handlers in the kernel, and it is a one-off host configuration step).
docker buildx build --platform linux/amd64,linux/arm64 \
-t auroralibros/aurora-api:1.3.0 --push ./api
docker buildx imagetools inspect auroralibros/aurora-api:1.3.0Name: docker.io/auroralibros/aurora-api:1.3.0
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:c41f8a2b...
Manifests:
Name: auroralibros/aurora-api:1.3.0@sha256:9e2a17f4...
Platform: linux/amd64
Name: auroralibros/aurora-api:1.3.0@sha256:5b70c3d8...
Platform: linux/arm64A manifest list (or image index) is an index that points to one image per platform. When somebody runs docker pull auroralibros/aurora-api:1.3.0, the client reports its architecture and the registry hands over the right manifest: the same tag works on the team's Mac and on the Graviton server, with no suffixes and no branches.
An important note: --platform with several architectures forces --push, because the daemon's local store cannot hold a multi-platform index. With --load you can only load one platform at a time.
TARGETPLATFORM and cross-compilation
TARGETPLATFORM and cross-compilationEmulating an architecture with QEMU works, but it is slow —three to ten times slower. The professional pattern is to compile natively and use emulation only for the final stage. BuildKit exposes automatic variables for exactly that:
| Variable | What it holds |
|---|---|
BUILDPLATFORM |
The platform of the machine doing the building (e.g. linux/amd64) |
TARGETPLATFORM |
The target platform (linux/arm64) |
TARGETOS, TARGETARCH, TARGETVARIANT |
Its components separately |
# The build stage ALWAYS runs on the native architecture: fast
FROM --platform=$BUILDPLATFORM node:22-alpine AS builder
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Building on $BUILDPLATFORM for $TARGETPLATFORM"
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev
COPY src/ ./src/
# The final stage does belong to the target architecture
FROM node:22-alpine AS production
WORKDIR /app
COPY --from=builder --chown=node:node /app .
USER node
CMD ["node", "src/server.js"]docker buildx build --platform linux/amd64,linux/arm64 --progress=plain \
-t auroralibros/aurora-api:1.3.0 --push ./api 2>&1 | grep BuildingBoth builds happen on amd64: the second one compiles for arm64 without emulating. With compiled languages (Go, Rust) the gain is enormous, because GOARCH=$TARGETARCH is all it takes. With Node.js the benefit is smaller —the code is interpreted— but the pattern still avoids emulating npm ci, which is the slow part.
- Outputs with
--output, --load and --push
--output, --load and --pushWith the docker-container driver, the result does not automatically go to your daemon: you have to say where you want it.
docker buildx build --load -t aurora-api:local ./api # to the local daemon
docker buildx build --push -t auroralibros/aurora-api:1.3.0 ./api # to the registry
docker buildx build --output type=local,dest=./output ./api # loose files
docker buildx build --output type=oci,dest=aurora-api-oci.tar ./api # OCI format| Output | What it produces | What for |
|---|---|---|
--load (= type=docker) |
An image in your daemon | Local development and testing |
--push (= type=image,push=true) |
An image in the registry | Publishing and multi-architecture |
type=local |
The last stage's files | Extracting compiled artifacts |
type=oci / type=tar |
An OCI archive or a tar of the filesystem | Signing, scanning, daemonless environments |
type=cacheonly |
Nothing; it only warms the cache | Pre-warming CI |
type=local has an elegant use: treating Docker as a reproducible build system and keeping only the result, with no image in between. And type=tar,dest=aurora-api.tar produces a tar of the filesystem for transport or analysis.
docker buildx bake
docker buildx bakeBuilding Aurora Libros takes several long commands that are easy to mistype. Bake declares them in a file:
# docker-bake.hcl — Aurora Libros S.L.
variable "VERSION" { default = "1.3.0" }
variable "REGISTRY" { default = "auroralibros" }
variable "PLATFORMS" { default = "linux/amd64,linux/arm64" }
group "default" {
targets = ["api", "web"]
}
target "common" {
platforms = split(",", PLATFORMS)
cache-from = ["type=registry,ref=${REGISTRY}/buildcache"]
cache-to = ["type=registry,ref=${REGISTRY}/buildcache,mode=max"]
labels = {
"org.opencontainers.image.version" = VERSION
"org.opencontainers.image.vendor" = "Aurora Libros S.L."
}
}
target "api" {
inherits = ["common"]
context = "./api"
target = "production"
tags = ["${REGISTRY}/aurora-api:${VERSION}", "${REGISTRY}/aurora-api:latest"]
args = { VERSION = VERSION }
}
target "web" {
inherits = ["common"]
context = "./web"
tags = ["${REGISTRY}/aurora-web:${VERSION}"]
}
# Matrix: the same image with several Node versions
target "api-matrix" {
inherits = ["common"]
context = "./api"
name = "api-node${node}"
matrix = { node = ["20", "22", "23"] }
args = { NODE_VERSION = node }
tags = ["${REGISTRY}/aurora-api:${VERSION}-node${node}"]
}docker buildx bake --print # see the plan without building
docker buildx bake # the "default" group: api and web
docker buildx bake api --set api.tags=aurora-api:local --load
VERSION=1.4.0 docker buildx bake --push
docker buildx bake api-matrixThree things make bake worth it: inherits saves you from repeating the shared configuration, the targets are built in parallel (here, api and web at the same time), and --print shows you the resolved plan before anything runs. Bake can also read a compose.yaml directly (docker buildx bake -f compose.yaml), taking advantage of the build sections you have already written.
Common Mistakes and Tips
Using the docker driver and expecting multi-architecture or remote cache. Create a docker-container builder with docker buildx create --use.
Forgetting --load or --push with docker-container. The build finishes successfully and the image is nowhere to be found.
Trying --load with several platforms. The local store does not hold multi-platform indexes. Use --push or load a single one.
Passing secrets with --build-arg. They stay in docker history. --mount=type=secret, always.
Believing the cache mount travels with the image. It is local to the builder. Between machines, use a remote cache.
Using mode=min for the remote cache of a multi-stage build. It only caches the final image; the expensive stages are rebuilt. Use mode=max.
Letting the builder's cache grow without limit. Run docker buildx prune --filter until=168h periodically, or use --keep-storage.
Tip: when a build fails incomprehensibly, the order is --progress=plain to see the full output, --no-cache to rule out a poisoned cache, and --target <stage> to isolate the exact point of failure. Those three resolve practically every case.
Exercises
Exercise 1. Create a docker-container builder, add a type=cache mount to aurora-api's npm install and measure the difference: build once, change package.json and rebuild, with and without the mount. Explain why the final image does not grow.
Exercise 2. Prove that --mount=type=secret leaves no trace: build the same image passing a token with --build-arg and with --secret, and search for the token in the history, in the environment and in each one's exported tarball.
Exercise 3. Publish auroralibros/aurora-api:1.3.0 for linux/amd64 and linux/arm64, inspect the resulting manifest list, and explain exactly what happens when an arm64 server and an amd64 laptop pull the same tag.
Solutions
Solution 1.
docker buildx create --name aurora --driver docker-container --use --bootstrap
docker buildx build --no-cache --target dependencies --load -t a:v1 ./api 2>&1 | tail -1
sed -i 's/"express": "4.19.3"/"express": "4.19.2"/' api/package.json
# Without a cache mount (Dockerfile.nocache)
time docker buildx build -f api/Dockerfile.nocache --target dependencies --load -t a:v2 ./api
# With a cache mount
time docker buildx build --target dependencies --load -t a:v3 ./api
docker image ls a --format "{{.Tag}}\t{{.Size}}"A change in package.json invalidates the COPY package.json layer and everything after it, so npm ci runs again in both cases. The difference is inside that run: without the mount, npm downloads the 64 packages all over again; with it, npm finds /root/.npm already populated from the previous build and only resolves the tree and links. The network drops out of the critical path and 6.8 seconds remain.
And the image weighs exactly the same (198 MB) because a cache mount takes no part in the layer: BuildKit mounts it before running the command and unmounts it afterwards, so by the time the layer's filesystem is consolidated, /root/.npm is no longer there. That is the essential difference from COPY: one leaves a mark, the other does not.
Solution 2.
TOKEN="npm_tok_a91f3c"
echo "$TOKEN" > /tmp/tok.txt
# A) With ARG
docker buildx build -f api/Dockerfile.arg --build-arg NPM_TOKEN="$TOKEN" --load -t leak:arg ./api
# B) With secret
docker buildx build --secret id=npm_token,src=/tmp/tok.txt --load -t secure:sec ./api
for img in leak:arg secure:sec; do
h=$(docker history --no-trunc "$img" | grep -c "$TOKEN")
e=$(docker image inspect "$img" --format '{{json .Config}}' | grep -c "$TOKEN")
t=$(docker save "$img" | strings | grep -c "$TOKEN")
echo "$img -> history:$h config:$e tarball:$t"
doneThe image built with ARG leaks the token through three independent channels: the build history (which keeps the RUN line with the value substituted in), the image configuration (where the ARG is recorded as metadata) and the layer content (where the .npmrc that was written and deleted is still present under a whiteout). Missing just one of them is enough for the secret to be published.
With --secret all three come back zero. BuildKit mounts the file as a tmpfs at /run/secrets/npm_token only for as long as that RUN lasts; the mount is not part of the filesystem that gets consolidated into the layer, and the value shows up in no metadata because it was never a build argument. It is, literally, the only correct way to use credentials during a build.
Solution 3.
docker run --privileged --rm tonistiigi/binfmt --install arm64 >/dev/null
docker buildx build --platform linux/amd64,linux/arm64 \
-t auroralibros/aurora-api:1.3.0 --push ./api
docker buildx imagetools inspect auroralibros/aurora-api:1.3.0 \
--format '{{range .Manifest.Manifests}}{{.Platform.OS}}/{{.Platform.Architecture}} {{.Digest}}{{"\n"}}{{end}}'
docker buildx imagetools inspect auroralibros/aurora-api:1.3.0 --raw | head -3linux/amd64 sha256:9e2a17f4c8b3...
linux/arm64 sha256:5b70c3d8a1e6...
{
"mediaType": "application/vnd.oci.image.index.v1+json",The 1.3.0 tag does not point at an image: it points at an index (image.index.v1+json) containing two manifests, each with its own digest and its declared platform.
When the arm64 server runs docker pull auroralibros/aurora-api:1.3.0, its client sends the types it understands in the Accept header and reports its platform; the registry returns the index, the client looks for the linux/arm64 entry, and downloads only the sha256:5b70c3d8... manifest and its layers. The amd64 laptop, with the same command and the same tag, gets sha256:9e2a17f4.... Neither downloads a single byte belonging to the other architecture.
The three practical consequences: compose.prod.yaml can carry one image reference valid for the whole fleet; if you pin by digest (lesson 05-03) you must pin the index's digest, not that of a specific platform, or you will break portability; and if one day somebody builds without --platform on their Mac and pushes with that same tag, they will replace the index with a lone arm64 image and the amd64 servers will fail with exec format error. That is why multi-architecture builds belong in the pipeline and not on anybody's laptop.
Conclusion
BuildKit has stopped being a black box. You know that it does not run the Dockerfile line by line but builds a dependency graph, parallelizes independent stages, transfers the context on demand and prunes whatever contributes nothing to the final image —hence the tests stage not running when you build production. You know how to verify it, how to read its output and, when something fails, how to turn on --progress=plain to see the real execution. And you know Buildx and its builders: the docker driver for the basics and docker-container for everything else, with its own cache and its emulated platforms.
You have mastered the four RUN mounts. type=cache turned a 39-second dependency reinstall into 7 seconds without the image growing by a byte, because the mount is not part of the layer. type=bind reads files without copying them, type=tmpfs provides temporary space in memory, and type=secret finally closes the problem you have been carrying since lesson 02-04: a token passed with --build-arg showed up in the history, in the configuration and in the tarball; passed with --secret, zero matches in all three. type=ssh completes the picture by forwarding your agent so you can clone private repositories without any key entering the build.
And you know how to work beyond your own machine: remote cache with --cache-from/--cache-to and its backends —registry, gha, local, inline— with mode=max so the intermediate stages get reused too, which will reduce the pipeline in lesson 06-02 to seconds; multi-architecture builds with QEMU and binfmt_misc, manifest lists that make a single tag serve both the team's Mac and the Graviton server, and the --platform=$BUILDPLATFORM pattern with TARGETARCH to compile natively instead of emulating. You finish with the --output outputs and with docker buildx bake, which reduces the entire Aurora Libros build to one docker buildx bake --push with inheritance, variables and matrices.
In lesson 05-06 you stop building and start observing. You will see the logging of a running platform: logging drivers with their comparison table, mandatory rotation and exactly what happens if you do not configure it, structured JSON logs with a request identifier, and an aggregation stack with Loki, Promtail and Grafana brought up as Aurora Libros' observability profile. Then metrics: docker stats and its limits, the daemon's Prometheus endpoint, cAdvisor and node-exporter, the metrics that really get watched, the alerts that deserve to wake somebody up, and the rich health endpoint pattern that turns /health into the source of truth for the whole platform.
Docker: From Beginner to Advanced
Module 1: Introduction to Docker
- What Is Docker?
- Installing Docker
- Docker Architecture
- Basic Docker Commands
- Understanding Docker Images
- Creating Your First Docker Container
- The Course Project: The Aurora Libros Platform
Module 2: Working with Docker Images
- Docker Hub and Repositories
- Building Docker Images
- Dockerfile Basics
- Advanced Dockerfile Instructions
- Managing Docker Images
- Tagging and Publishing Images
Module 3: Docker Containers
- Running Containers
- Container Lifecycle
- Managing Containers
- Inspecting and Debugging Containers
- Docker Networking
- Data Persistence with Volumes
- Resource Limits and Restart Policies
Module 4: Docker Compose
- Introduction to Docker Compose
- Defining Services in Docker Compose
- Docker Compose Commands
- Multi-Container Applications
- Environment Variables in Docker Compose
- Profiles, Overrides and Multiple Environments
- Local Development with Docker Compose
Module 5: Advanced Docker Concepts
- Docker Networking Deep Dive
- Docker Storage Options
- Docker Security Best Practices
- Optimizing Docker Images
- Advanced Builds with BuildKit and Buildx
- Logging and Monitoring in Docker
- The Runtime Inside: Namespaces, Cgroups and Layers
Module 6: Docker in Production
- Preparing an Image for Production
- CI/CD with Docker
- Orchestrating Containers with Docker Swarm
- Introduction to Kubernetes
- Deploying Docker Containers in Kubernetes
- Scaling and Load Balancing
- Deployment Strategies and Rollback
