{"id":28456787,"url":"https://github.com/vojtechmares/docker-training","last_synced_at":"2026-02-22T13:39:46.701Z","repository":{"id":296290593,"uuid":"982846950","full_name":"vojtechmares/docker-training","owner":"vojtechmares","description":"Training on Docker","archived":false,"fork":false,"pushed_at":"2025-06-12T14:09:07.000Z","size":35,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-29T10:39:11.485Z","etag":null,"topics":["training-materials"],"latest_commit_sha":null,"homepage":"https://www.mares.cz/skoleni/docker","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vojtechmares.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-13T13:47:39.000Z","updated_at":"2025-06-12T14:08:51.000Z","dependencies_parsed_at":"2025-06-03T03:48:46.268Z","dependency_job_id":null,"html_url":"https://github.com/vojtechmares/docker-training","commit_stats":null,"previous_names":["vojtechmares/docker-training"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vojtechmares/docker-training","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fdocker-training","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fdocker-training/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fdocker-training/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fdocker-training/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vojtechmares","download_url":"https://codeload.github.com/vojtechmares/docker-training/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojtechmares%2Fdocker-training/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29714640,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T13:30:57.152Z","status":"ssl_error","status_checked_at":"2026-02-22T13:30:28.561Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["training-materials"],"created_at":"2025-06-06T23:08:49.754Z","updated_at":"2026-02-22T13:39:46.693Z","avatar_url":"https://github.com/vojtechmares.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdownlint-disable MD033 --\u003e\n\u003c!-- markdownlint-disable-next-line MD041 --\u003e\n\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\" style=\"font-size: 4rem;\"\u003eDocker Training\u003c/h1\u003e\n  \u003cp align=\"center\" style=\"font-size: 1.5rem;\"\u003e\n    Reliable builds at every environment and easy software distribution!\n  \u003c/p\u003e\n  \u003cp align=\"center\"\u003e\n    \u003ca href=\"https://www.docker.com/\"\u003e\u003cimg alt=\"Docker\" src=\"https://img.shields.io/badge/TRAINING ON-DOCKER-2496ED?style=for-the-badge\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://www.mares.cz\"\u003e\u003cimg alt=\"Vojtěch Mareš\" src=\"https://img.shields.io/badge/TRAINING BY-Vojtěch Mareš-fd9a00?style=for-the-badge\"\u003e\u003c/a\u003e\n  \u003c/p\u003e\n  \u003cp align=\"center\"\u003e\u003ca href=\"https://www.mares.cz\"\u003eVojtěch Mareš\u003c/a\u003e | \u003ca href=\"mailto:vojtech@mares.cz\"\u003evojtech@mares.cz\u003c/a\u003e\u003c/p\u003e\n\u003c/p\u003e\n\u003c!-- markdownlint-enable MD033 --\u003e\n\n## About the course\n\n### Contribute\n\nThe course is forever open-source, so feel free to fork, create issues, contribute or just download the materials.\n\n## About the lector\n\nHi 👋, I'm Vojtěch Mareš and I will be your lector. I am a freelance DevOps architect, consultant and lector. I work with clients to improve their systems with\n\n- CI pipelines\n- Containerization with Docker\n- Application orchestration on Kubernetes\n- Observability with Prometheus, Grafana, Loki and Open Telemetry\n- Infrastructure either on cloud or on-premise managed with Terraform\n- I work with: Git, Kubernetes, Terraform, GitHub, GitLab, Prometheus, Grafana, Docker, PostgreSQL, and more...\n\n## What is Docker\n\nDocker is today's de-factor standard for building, shipping and running containerized applications.\n\n### Docker\n\nDocker is: the company, the CLI, the system daemon. See [Docker glossary](#docker-glossary) for distinction between those terms.\n\n### Image\n\nImage is a an artifact produced by a build process using the `Dockerfile` which is a \"recipe\" how to build the image.\n\n### Container\n\nContainer is a running process based on an image.\n\n### Use cases for containers\n\nMost of modern world's backends are run in containers. Containers make building, deploying, developing and distribution easy.\n\nThanks to the extensive toolchain around containers and also containers are not very complicated technology – they are not virtualization, they are just \"walls around processes\".\n\nTodays common use-cases are:\n\n- simplification of software distribution – pull an image and run it almost anywhere\n- simplified local development – no need to install dependencies on your machine, everything is nicely packed inside the image already\n- runtime orchestration – because container images are easy to distribute and relatively cheap to start, they became de-facto standard for running applications thanks to tools like Kubernetes\n\n## Docker glossary\n\n| **Term** | **Explanation** |\n| -------- | --------------- |\n| Docker Inc. | The company behind Docker |\n| Docker daemon | A container runtime by Docker Inc. |\n| Image | An artifact produced by a build process |\n| Container | A running process of given image |\n| Dockerfile | A build \"recipe\" on how to build an image |\n| Containerfile | Basically a Dockerfile, just by Red Hat |\n| Container runtime | A universal name for running containers, such as Docker daemon, containerd, CRI-O and others |\n| Open Container Initiative (OCI) | Organization responsible for maintaining the Image, Runtime, and Distribution specifications |\n| Container Runtime Interface (CRI) | An API specification for running containers |\n| Container Network Interface (CNI) | An API specification for container networking |\n\n## What are containers\n\nContainers are lightweight process isolation and software delivery solution.\n\n1. Build it.\n1. Push it.\n1. Pull it.\n1. Run it.\n\n### Containers are not virtualization\n\nStandard virtualization creates an entire new linux machine on a physical host, running a new instance of system Kernel, init process (for example: systemd) and then running everything again on the guest system.\n\nMeanwhile containers share the same Kernel and are created using Kernel primitives – namespaces and cgroups. These primitives ensure process isolation and separating the \"insides\" of container from the host system (users, filesystem,...).\n\nContainers ensure that each process has its own space on filesystem, network, memory and CPU. With containers you can control all of these resources easily.\n\n## 12 Factor Apps\n\nSee: [12factor.net](https://12factor.net/)\n\nTwelve rules to build better and self-contained apps.\n\n## Docker Docs\n\nSee: https://docs.docker.com/\n\n## Setup\n\n### Install Docker\n\nSee:\n\n- https://www.docker.com/\n- https://docs.docker.com/desktop/\n- https://docs.docker.com/engine/\n- https://docs.docker.com/compose/\n\n### Visual Studio Code extension\n\nSee: https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker\n\n## System commands\n\n- `docker version`\n- `docker info`\n- `docker system df`: Show Docker disk usage\n- `docker system prune`\n  - `--all`: Remove all unused images not just dangling ones\n  - `--volumes`: Prune anonymous volumes\n  - `--force`: Do not prompt for confirmation\n\n## Working with images\n\n- `docker pull \u003cimage\u003e` - download an image\n- `docker image ls` - list all images\n- `docker image ls -q` - quiet output, just IDs\n- `docker image ls \u003cimage\u003e` - list image versions\n- `docker image rm \u003cimage\u003e` - remove image\n- `docker image inspect \u003cimage\u003e` - show image properties\n\n## Start a container\n\n```bash\ndocker run ubuntu\n```\n\nRun with custom command:\n\n```bash\n# print /etc/os-release file from inside the container\ndocker run ubuntu cat /etc/os-release\n```\n\nStandard tty interactive access:\n\n```bash\ndocker run -it ubuntu\n```\n\n\u003e `-it` means interactive, it will attach to your session and you will be able to run a bash commands in this example.\n\nRun image in background:\n\n```bash\n# sleep for 30s and then exit\ndocker run -d ubuntu sleep 30\n```\n\n\u003e `-d` means detached, if you want to run the container in the background.\n\nCommon parameters:\n\n- `--name \u003cname\u003e` - set container name (Wozniak easter egg)\n- `-d` - run in detached mode\n- `-it` - map TTY a STDIN (for bash eg.)\n- `-e \u003cvariable\u003e=\u003cvalue\u003e` - set ENV variable\n- `--env-file=\u003cenv_file\u003e` - load all variables defined in ENV file\n\n\u003e [!NOTE]\n\u003e\n\u003e All parameters must always go before `\u003cimage\u003e`, otherwise the arguments will be passed to the container process as arguments.\n\u003e\n\u003e ```bash\n\u003e # Will work:\n\u003e docker run -it ubuntu\n\u003e\n\u003e # Won't work:\n\u003e docker run ubuntu -it\n\u003e ```\n\n## Working with containers\n\n- `docker container ls` - list containers\n- `docker ps`- list containers\n- `docker start \u003ccontainer\u003e`\n- `docker stop \u003ccontainer\u003e`\n- `docker restart \u003ccontainer\u003e`\n- `docker rm \u003ccontainer\u003e` - remove container\n\nRestart policy:\n\nDefault behavior is, that when container stops or fails, it will stay stopped. This can be changed with `--restart \u003crestart policy\u003e`\n\n- `--restart on-failure` - restart only when container return non zero return code\n- `--restart always` - always, even on Docker daemon restart (server restart also)\n- `--restart unless-stopped` - similar to always, but keep stopped container stopped on Docker daemon restart (server restart also)\n\nIf you want to set maximum restart count for on-failure restart policy, you can use: `--restart on-failure:\u003ccount\u003e`\n\nList containers:\n\n- `docker container ls` - list running containers\n- `docker container ls -a` - list all containers\n- `docker container ls -a -q` - list IDs of all containers\n\nor alternatively use short syntax without the \"container\" word:\n\n- `docker ps` - list running containers\n- `docker ps -a` - list all containers\n- `docker ps -a -q` - list IDs of all containers\n\n## Container logs\n\n```bash\ndocker logs \u003ccontainer\u003e\n```\n\nPrint logs from container.\n\nArguments\n\n- `-f` - follow, works like `tail -f`\n- `-t` - show time prefix\n\nExample:\n\n```bash\n# Start the container\ndocker run --name postgres-16 -d -e POSTGRES_PASSWORD=postgres postgres:16\n\n# Get container logs\ndocker logs -f postgres-16\n```\n\n## Execute a command inside container\n\n```bash\ndocker exec \u003ccontainer\u003e \u003ccommand\u003e\n```\n\nExecute a command inside a container.\n\nArguments can be used:\n\n- `-it` to run it interactively\n- `-e` to se environment variable\n- `-d` to run in detached mode\n- `-u` run as user\n\nPostgreSQL example:\n\n```bash\n# Start the container\ndocker run --name postgres-16 -d -e POSTGRES_PASSWORD=postgres postgres:16\n\n# Execute command inside\ndocker exec -it postgres-16 bash\n# or\ndocker exec -it -u postgres postgres-16 psql\n```\n\n## Build an image\n\n- `docker build`\n- Dockerfile\n\n```dockerfile\nFROM ubuntu:24.04\n\nRUN apt-get update \u0026\u0026 apt-get install curl tz-data ca-certificates\n\nCOPY local/path in/container/path\n\nCMD [ \"/bin/bash\" ]\n```\n\nDockerfile keywords:\n\n- `FROM` - select base image, default is `scratch`\n- `COPY` - copy files from host system to the image\n- `RUN` - run a command during build (e.g. `apt-get install ...`)\n- `CMD` - command to be executed on container start\n- `ENV` - set environment variable\n- `ARG` - build argument, works like environment variable but is only available during build time\n- `EXPOSE` - define which port will be exposed\n- `WORKDIR` - change the current working directory\n- `USER` - change current user (user must exist)\n- `VOLUME` - define volume\n- `ADD` - instead of copy, archives added by add are extracted\n\n`.dockerignore` - similar to `.gitignore`, to ignore files during build, useful with `COPY` when you do not want to copy files, e.g. build cache, locally installed dependencies, etc.\n\n```dockerignore\nnode_modules\n.vscode\nREADME.md\n.git\nout\n.DS_Store\n```\n\nBuild image:\n\n- `docker build \u003cpath\u003e` - build an image, path is relative context for `COPY` etc.\n- `docker build -t \u003ctag\u003e \u003cpath\u003e` - build image with tag (can include registry)\n- `docker build -f \u003cpath-to-dockerfile\u003e -t \u003ctag\u003e \u003cpath\u003e` - custom path to Dockerfile, default is `./Dockerfile`\n- `docker tag \u003cold tag\u003e \u003cnew tag\u003e` - creates a new tag for image\n\nCross-platform build:\n\n```bash\ndocker build --platform linux/amd64 .\n\n# multiplatform\ndocker build --platform linux/amd64,linux/arm64 .\n```\n\n## Port-forwarding\n\nOr port-publishing. Aka you expose a port from the container to the host system.\n\n```bash\ndocker run -p [\u003cnetwork-addr\u003e:]\u003chost-port\u003e:\u003ccontainer-port\u003e \u003cimage\u003e\n```\n\n```bash\ndocker run -ti -p 8080:80 nginx\ndocker run -ti -p 127.0.0.1:8080:80 nginx\n```\n\n\u003e [!NOTE]\n\u003e\n\u003e Its recommended to specify the address, especially when deploying to production, since a port is attached to a network interface directly, this bypasses firewall rules (iptables, etc.) and will expose your app to public internet. A common use-case is to run app behind a reverse proxy (NGINX, Traefik, Caddy,...). By attaching port to local IP (`127.0.0.1`) or other non public CIDR, app will be accessible only through the proxy and not via public server IP.\n\n## Container volumes\n\nSee: https://docs.docker.com/engine/storage/volumes/\n\nVolumes are used for persistent data storage for containers and can share data between containers. Data are written directly to the host.\n\n- `docker volume` - all volume management commands\n- `docker volume ls` - list all volumes\n- `docker volume rm \u003cvolume\u003e` - remove volume\n- `docker volume prune` - remove all not used (not bound to container) volumes\n\nExamples:\n\n```bash\ndocker run -ti -v /data ubuntu\ndocker run -ti -v my-volume:/data ubuntu\ndocker run -ti -v $(pwd)/my-data:/data ubuntu\n```\n\nGet volume paths for an image:\n\n```bash\ndocker image inspect redis --format \"{{.Config.Volumes|json}}\"\n\ndocker image inspect postgres:16 --format \"{{.Config.Volumes|json}}\"\n```\n\nRead only volumes:\n\nIn case you want read only volume (configuration for example), just add a `:ro` suffix to the volume, to make it read only.\n\n```bash\ndocker run -it -v /data:ro ubuntu\n\n# try writing to the volume (must be executed inside the container)\necho \"Hello world\" \u003e\u003e /data/hello.txt\n```\n\nShow all volumes and mounts for all containers:\n\n```bash\ndocker ps -a --format '{{ .ID }}' | xargs -I {} docker inspect -f '{{ .Name }} ({{ .ID }}){{ printf \"\\n\" }}{{ range .Mounts }}{{ printf \"\\n\\t\" }}{{ .Type }} {{ if eq .Type \"bind\" }}{{ .Source }}{{ end }}{{ .Name }} =\u003e {{ .Destination }}{{ end }}{{ printf \"\\n\" }}' {}\n```\n\nFind container with specific volume:\n\n```bash\ndocker ps -a --filter volume=\u003cvolume\u003e\n```\n\nSocket forwarding:\n\n\u003e [!NOTE]\n\u003e\n\u003e If you want to expose host machine sockets, you can use volumes to do so. But keep in mind that read only volumes does not work for this use-case!\n\n```bash\ndocker run -v /var/run/docker.sock:/var/run/docker.sock docker docker ps\n```\n\n\u003e [!WARNING]\n\u003e\n\u003e Exposing sockets directly is a big security risk and should not be done in production unless you really know what are you doing!\n\n\u003e [!WARNING]\n\u003e\n\u003e !! Possible Security Risk !!\n\u003e Since you can mount your host's rootfs to container with root privileges. Every process with access to Docker or Docker socket potentially has root privileges on host system!\n\u003e\n\u003e userns-remap can fix that\n\u003e\n\u003e ```bash\n\u003e docker run -v /:/rootfs -ti ubuntu\n\u003e ```\n\nuserns-remap:\n\nDocker can remap root user (or any other) user from container to higher-ID user on host system.\n\nSee: https://docs.docker.com/engine/security/userns-remap/\n\nThis has to be explicitly enabled.\n\nAs a dockerd argument\n\n```bash\ndockerd --userns-remap=\"default\"\n```\n\nVia config file (`/etc/docker/daemon.json`)\n\n```json\n{\n  \"userns-remap\": \"default\"\n}\n```\n\n## Image tags\n\nTo differentiate between image versions etc., use image tags – the string after colon (`:v1.3.4`). Its common to use Git commit hash and/or version.\n\nThere is also a special `latest` tag, which is the default tag to be used, if none is specified.\n\nEither create tag on build or re-tag an existing image for example. To re-tag existing image, the image must be present locally if working with Docker, or use other tools that allow for working with remote registries if re-tagging image within same registry.\n\n```bash\n# build image with tag\ndocker build -t ttl.sh/docker-training-build-example:1h\n\n# re-tag existing image\ndocker tag ubuntu:24.04 ttl.sh/docker-training-base:48h\n```\n\n### Immutable tags\n\n## Push images\n\n- `docker push`\n\n```bash\ndocker push ttl.sh/docker-training-demo:1h\n```\n\n## Pull images\n\n```bash\ndocker pull ttl.sh/docker-training-demo:1h\n```\n\n## Image Registry (OCI Artifact Registry)\n\nRegistries can be public (pull image without authentication) or private.\n\nPublic \u0026 free registries:\n\n- Docker Hub (default registry)\n- GitHub Container Registry (ghcr.io)\n- Quay.io\n- ttl.sh (for debugging)\n\n### Private registries\n\nCloud:\n\n- [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/)\n- [Google Cloud Artifact Registry](https://cloud.google.com/artifact-registry/docs)\n- [Azure Container Registry](https://azure.microsoft.com/en-us/products/container-registry)\n- [Digital Ocean Registry](https://www.digitalocean.com/products/container-registry)\n- and more...\n\nSelf-hosted:\n\n- Distribution – official implementation of image registry, open source\n- Harbor – open source OCI registry with scanning via Trivy support and service accounts for program access\n- GitLab – open source core Git repository hosting with built-in image registry\n- and more...\n\n## Docker in Docker (DinD)\n\nRun Docker in Docker:\n\n```bash\n# Start DinD container\ndocker run --name docker -d --privileged docker:dind\n\n# Test it\ndocker exec docker docker info\ndocker exec docker docker image ls\ndocker exec docker docker run hello-world\ndocker exec -ti docker sh\n```\n\nThis is a common case for GitLab CI runner, running Docker in Docker either with its `services` directive or via exposed socket (insecure!).\n\n## Lint `Dockerfile`\n\nTo avoid mistakes, use [Hadolint](https://github.com/hadolint/hadolint) to lint your Dockerfiles.\n\nIt works like every other linter, checks for common mistakes, etc.\n\n```bash\nhadolint \u003cpath-to-dockerfile\u003e\n```\n\n## Docker networks\n\n- `docker network ls`\n- `docker network create \u003cnetwork\u003e`\n- `docker network rm \u003cnetwork\u003e`\n\nCreate network:\n\n```bash\ndocker network create -d bridge my_bridge\n```\n\nRun \u0026 attach containers:\n\n```bash\n# Run on network\ndocker run -d --net=my_bridge --name nginx nginx\n\n# Connect to network\ndocker run -d --name nginx-2 nginx\ndocker network connect my_bridge nginx-2\n```\n\nTest the network:\n\n```bash\ndocker run -ti --net my_bridge curl bash\n```\n\n## Docker Compose\n\nDocker Compose (or docker-compose for v1 cli).\n\nDocker Compose is configured with YAML config file `compose.yml` (or with `.yaml` extension, or `docker-compose.yml` for as old file name).\n\n```yaml\nversion: \"3.9\"\nservices:\n  nginx:\n    image: nginx:latest\n    container_name: nginx\n    ports:\n      - \"8080:8080\"\n  app:\n    image: dummy-app\n  postgresql:\n    image: postgresql:17\n    ports:\n      - \"5432:5432\"\n```\n\n### `compose.yaml` file\n\nSee: [Docker Compose file reference](https://docs.docker.com/reference/compose-file/)\n\n### Docker Compose services\n\n```yaml\nversion: \"3\"\n\nservices:\n  app:\n    image: node:latest\n  database:\n    image: postgresql:17\n  # ...\n```\n\n### Docker Compose ports\n\n```yaml\nversion: \"3\"\n\nservices:\n  nginx:\n    image: nginx:latest\n    ports:\n      - \"8080:80\" # host:container\n```\n\n### Docker Compose volumes\n\n```yaml\nversion: \"3\"\n\nservices:\n  database:\n    image: postgresql:17\n    environment:\n      POSTGRES_PASSWORD: postgres\n      POSTGRES_USER: postgres\n      POSTGRES_DATABASE: app\n    volumes:\n      - db-data:/var/lib/postgresql/data\n\nvolumes:\n  db-data:\n```\n\n### Docker Compose networks\n\nLink services together:\n\n```yaml\nservices:\n  web:\n    image: nginx\n    ports:\n      - \"8080:80\"\n    links:\n      - \"db:database\"\n  db:\n    image: postgres\n    ports:\n      - \"5432:5432\"\n```\n\nNetworks:\n\n```yaml\nversion: \"3\"\n\nservices:\n  nginx:\n    image: nginx:latest\n    ports:\n      - \"8080:80\" # host:container\n    networks:\n      - demo-net\n\nnetworks:\n  demo-net:\n    driver: bridge\n    # custom driver options\n    driver_opts:\n      com.docker.network.bridge.host_binding_ipv4: \"127.0.0.1\"\n```\n\n### Run Docker Compose\n\n```bash\n# start\ndocker compose up\n\n# start in detached mode\ndocker compose up -d\n\n# stop\ndocker compose down\n```\n\n### Build with Docker Compose\n\n```bash\ndocker compose build\n\n# run\ndocker compose up -d\n```\n\n## cAdvisor\n\n[cAdvisor](https://github.com/google/cadvisor) is a tool that analyzes resource usage and performance characteristics of running containers. Its developed by Google, but its not an official product.\n\n## Containers in production\n\nAlready mentioned Docker Compose - its fine for a single server.\n\nOr...\n\n### Kubernetes\n\nOr you can opt in for something bigger like Kubernetes to run your applications in production on scale.\n\n### Managed container platforms\n\n- Google Cloud Run\n- AWS ECS\n- Azure Containers\n- Fly.io\n- Render\n- Knative (on top of Kubernetes)\n\n## OCI\n\nOCI aka Open Container Initiative is an organization backing the image, runtime and distribution specification, to ensure a well-maintained and well-designed APIs.\n\nTo work with OCI registry/images/etc., you can use the following tools\n\n- crane\n\n    [crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane) is a tool for interacting with remote images and registries.\n\n    Install on Mac:\n\n    ```bash\n    brew install crane\n    ```\n\n    **Copy Image**\n\n    Copy image from one registry to another without pulling it to local machine.\n\n    `crane copy \u003csource\u003e \u003cdestination\u003e`\n\n    Example:\n\n    ```bash\n    crane copy ubuntu ttl.sh/training-ubuntu\n    ```\n\n- [ORAS](https://oras.land/)\n\n    Distribute Artifacts across OCI registries with easy.\n\n## Best practices\n\nCollection of best practices to optimize your images and minimize attack surface.\n\n### Optimize builds\n\n- Cache images\n- Optimize layers (`ENV`, `EXPOSE`, `WORKDIR` go first)\n- Use cache mounts (see: https://docs.docker.com/build/cache/optimize/#use-cache-mounts)\n\n    ```dockerfile\n    FROM node:latest\n    WORKDIR /app\n    RUN --mount=type=cache,target=/root/.npm npm install\n    ```\n\n### Single process\n\nThe best practice is to run only a single process inside a container.\n\nThen you do not need to handle signal propagation to gracefully shutdown everything.\n\nBut, there are exceptions. But consider this wisely. A common case of multi-process containers are databases such as PostgreSQL or MySQL, to run for example a backup process and the database core process itself.\n\nFor example NGINX which usually runs a master process and then multiple worker processes. In a container, run NGINX with a single worker and scale via launching multiple containers.\n\n### Multi-platform images\n\nWith Docker, you can create a multi-platform images, for example with multiple tags and create a manifest, which acts like an index for the platforms.\n\n### Start binary, not shell\n\nUse the `CMD [ \"/my/app\", \"arg\" ]` notation instead of `CMD command`.\n\nThe `CMD command` starts a shell process and in the shell then executes your program.\n\nThe shell does not handle signals and therefore your app does not receive them either, making graceful shutdown impossible.\n\nUsing the `CMD [ \"/my/app\", \"arg\" ]` option, this starts the binary under the given path. And system signals are sent directly to the main process.\n\n### Multi-stage builds\n\nWith multi-stage builds, you use multiple `FROM`s inside your Dockerfile, creating stages.\n\nIn each stage, you can perform a certain step – install dependencies only required for build, build the app and then copy the final binary to the runtime image.\n\nWIth multi-stage builds, you also limit the amount of dependencies and programs in your final image, minimizing the attack surface.\n\n### Mount secrets on build instead of copying them\n\nTo avoid leaking secrets (even when deleted from image in the end), mount files, for example `.npmrc` to the image on build instead of copying the file, which even when deleted would remain in the image metadata.\n\n```bash\ndocker build --secret id=yoursecret,src=/host/secret/file/path\n```\n\nYou can give a single `RUN` instruction access to this secret. By default this creates a file in `/run/secrets/\u003csecret-id\u003e`, but you can also specify a target path of your choice.\n\n```dockerfile\nRUN --mount=type=secret,id=yoursecret ...\nRUN --mount=type=secret,id=yoursecret,target=/target/path/to/secret ...\n```\n\nExample for AWS `credentials` file:\n\n```bash\ndocker build --secret id=aws,src=$HOME/.aws/credentials .\n```\n\n```dockerfile\n# consume the secret\nRUN --mount=type=secret,id=aws \\\n    AWS_SHARED_CREDENTIALS_FILE=/run/secrets/aws \\\n    aws s3 cp ...\n```\n\n### Rootless images\n\nImprove security by running your containerized app under a different user then root with the least privileges required.\n\nIf you app is running for example an HTTP server, the server must be bind to port higher then 1024, since those are reserved and required either root user or `NET_BIND_SERVICE` Kernel capability.\n\n```dockerfile\nFROM ubuntu:24.04\n\nRUN useradd bob\n\nUSER bob\n\nCMD whoami\n```\n\nValidate:\n\n```bash\n$ docker build -t rootless examples/rootless\n# ...\n\n$ docker run rootless\nbob\n```\n\n### Minimalistic base image\n\nUse minimalistic base images like `debian:slim` or `alpine` instead of full blown image.\n\nBut make sure that your app works on Alpine Linux for example, since its using musl instead of the common libc library.\n\n[Distroless](https://github.com/GoogleContainerTools/distroless) are special minimalistic images containing only a libc or a runtime like Node.js or Python. Therefore you have to use multi-stage builds to build your app. Distroless images do not offer a way to install system dependencies via apt, therefore your app has to be self-contained binary, for example Golang or cannot require any system libraries.\n\nSlim base images:\n\n- `debian:slim`\n- `alpine`\n- `distroless`\n\n### Image signing\n\nTo improve your image security and trust-worthiness, sign your images with your private key and validate with public key before starting the image, making sure that the image is the one you want and it was not tampered with (compromised registry or man-in-the-middle attack).\n\n- `cosign sign`\n\n### SBOMs\n\nSBOM or Software Bill Of Materials\n\nSBOMs contain list of all tech, libs, etc. used in the image and their respective version. Thanks to that, security can be accessed based on metadata, prove that image does not contain unnecessary tools, etc. and also track CVEs and patch images respectively.\n\nInspect SBOMs of an image with `docker scout`:\n\n```bash\n$ docker scout sbom --format list \u003cimage\u003e\n\n$ docker scout sbom --format list ubuntu:24.04\n{\"level\":\"info\",\"msg\":\"SBOM obtained from attestation, 131 packages found\\n\",\"time\":\"2025-05-28T00:34:31+02:00\"}\n\n         Name                    Version             Type\n─────────────────────────────────────────────────────────────\n  acl                  2.3.2-1build1.1               deb\n  apt                  2.7.14build2                  deb\n  attr                 1:2.5.2-1build1.1             deb\n  audit                1:3.1.2-2.1build1.1           deb\n  base-files           13ubuntu10.2                  deb\n  base-passwd          3.6.3build1                   deb\n  bash                 5.2.21-2ubuntu4               deb\n  bsdutils             1:2.39.3-9ubuntu6.2           deb\n  bzip2                1.0.8-5.1build0.1             deb\n# ... (shortened)\n```\n\nAdd as build attestation:\n\n```bash\ndocker build --tag \u003cimage\u003e \\\n  --attest type=sbom,generator=docker/scout-sbom-indexer:latest \\\n  --push .\n```\n\n### Image scanning\n\n- [Trivy](https://trivy.dev)\n- Harbor - uses Trivy to scan on push or continuously scan images withing the registry\n- validate image signature\n  - on push\n  - before running\n\n### Migrate from Docker Compose to Kubernetes with \"Kompose\"\n\nSee: https://kompose.io/\n\nKompose \"translates\" `docker-compose.yaml` file to Kubernetes manifests.\n\n## Docker alternatives\n\nYou may not be able to use Docker, either due to corporate licensing or you simple want only open-source software.\n\n### Podman\n\n[Podman](https://podman.io/) and [Podman Desktop](https://podman-desktop.io/) are RedHat developed open-source alternatives to Docker and Docker Desktop respectively. Both are open-source and can be used as a 1:1 Docker replacement.\n\n### Rancher Desktop\n\n[Rancher Desktop](https://rancherdesktop.io/) in another open-source Docker Desktop replacement developed by SUSE Rancher.\n\n### OrbStack\n\n[OrbStack](https://orbstack.dev/) is fast, but paid Docker Desktop alternative only for Mac.\n\n### containerd\n\n[containerd](https://containerd.io/) is above all a container runtime, but with its CLI tool `nerdctl` and integration with BuildKit, it can be used as a Docker replacement.\n\nMacOS support is buggy.\n\n### portainer.io\n\n[portainer.io](https://www.portainer.io/) is a Web UI for Docker. It helps you easily manage containers on your sever(s) or local machine.\n\n## Thank you! \u0026 Questions?\n\nThat's all, thank you for your attention.\n\nQuestions?\n\nLet's go for a beer 🍻.\n\n## Vojtěch Mareš\n\n- email: [vojtech@mares.cz](mailto:vojtech@mares.cz)\n- web: [mares.cz](https://www.mares.cz)\n- x (twitter): [@vojtechmares_](https://x.com/vojtechmares_)\n- linkedin: [/in/vojtech-mares](https://www.linkedin.com/in/vojtech-mares/)\n\nDid you like the course? Tweet a recommendation on X (Twitter) and tag me (@vojtechmares_) and/or add me on Linked In and I will send you a request for recommendation. Thanks!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvojtechmares%2Fdocker-training","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvojtechmares%2Fdocker-training","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvojtechmares%2Fdocker-training/lists"}