Docker emerged in 2013 as an open source tool to facilitate the creation and deployment of applications in containers, solving many common problems related to consistency and portability in development and production environments.
Before Docker, it was very difficult to ensure that a development environment was identical to the production environment. This caused the famous "works on my machine" problems. In addition, bringing up new environments and provisioning machines was slow and tedious.
Docker solves these problems by packaging applications along with their dependencies into isolated and portable containers. This ensures consistency and speed of deployment, allowing developers to work locally in the same way that the application is deployed in production.
The Docker company creates products to monetize around the Docker ecosystem.
- Build Images - Docker Build (Build Kit)
- Distribute the Images
- Run Containers - Containerd (Docker Runtime)
- Communication between Containers - Network
- File Sharing - Volumes
- Orchestration between Containers
This Engine runs through the Docker Desktop application which was made for Linux. So on Mac and Windows it runs on a Linux virtual machine to run the Docker Engine.
But it is also possible to run it through other applications such as colima.
Virtual machines (VMs) are an abstraction of physical hardware that turns one server into multiple servers. The hypervisor allows multiple VMs to run on a single machine. Each VM includes a full copy of an operating system, the application, necessary binaries and libraries – taking up tens of GBs. VMs can also be slow to boot up and maintaining (updating) them is more costly.
Containers are an evolution/optimization of virtual machines. They are an application layer abstraction that packages code and dependencies. Multiple containers can run on the same machine and share the kernel with other containers, each running as isolated processes in user space. Containers take up less space than VMs (container images are usually tens of MBs), can handle more applications and require fewer VMs and operating systems.
Run a new container:
docker run hello-world
Show all containers that are running and with the "-a" flag (all) also shows those that are not currently running.
docker ps -a
Run a command inside the container iteratively (flag -i), with a communication interface (flag -t for teletype terminal or tty) and using root user (flag -u):
docker run -it -u root ubuntu bash
Download images from Docker Hub:
docker pull nginx
Redirect container ports to ports on your machine:
docker run -p 8080:80 nginx
NOTE: Using higher ports on the computer so you don't have to run SUDO admin command. The first port is the one exposed to the computer and the second is the container.
Execute a command in a running container:
docker exec container_name ls
The --rm flag in Docker is used to automatically remove the container when it exits. Here is an example running a Docker container with --rm:
docker run --rm ubuntu echo "Hello World"
- Runs a command in a new container. This creates a new container instance from the given image and prepares it for running the given command. Once the command exits, the container stops.
- Essentially docker run runs a new, one-off container for running the specified command. The container is temporary and removed after running.
- Runs a command in an existing running container. This allows you to execute additional commands or a new interactive shell inside a running container.
- The container keeps running after the docker exec command completes. It does not create a new container, it simply lets you issue more commands to an already running container.
When a container dies we lose the changes inside it, because when it is reassembled, it follows the image specifications. To work around this, we use shared volumes.
Share volumes between the container (/usr/share/nginx/html) and your machine (current_folder/html):
docker run -p 8080:80 -v $(pwd)/html:/usr/share/nginx/html nginx
The Dockerfile is a recipe for building automatic Docker images. It allows you to define a standardized and immutable environment for applications, ensuring that they are always run the same way, regardless of where they are deployed. We always start from a base image. The larger your image, the slower it will take to upload your container.
We can add a stage to the build steps:
# Base image
FROM node:16-alpine AS base
# Install dependencies apenas nesta etapa
COPY package*.json ./
RUN npm install
# Stage de build
FROM base AS build
COPY . .
RUN npm run build
# Stage de produção
# Copia apenas o necessário
COPY --from=build /app/package*.json ./
COPY --from=build /app/dist ./dist
# Use o usuário padrão node
CMD [ "node", "dist/index.js" ]
And run with the --target build flag to go to the desired stage:
docker build --target build2
It is important to define the USER to restrict image user permissions. If there is volume synchronization, it is a good idea to set the same USER that you use in development on your machine with the container's USER so you don't need to change files with SUDO.
It is the container template, it is the descriptive of the container instance. It is like a class (specification) of the container.
Build an image based on the Docker File:
docker build -t kibolho/nginx:latest .
Docker Hub is a public registry of Docker images. It allows developers to share and reuse components already built by others, avoiding the need to create everything from scratch. You can pull existing images or upload your own images created locally. Always use reliable/verified images.
Upload an image to Docker Hub
docker push kibolho/nginx:latest
Docker Compose facilitates the definition and execution of multi-container Docker applications (orchestrator). With Compose, you can configure your services in YAML files and deploy your entire stack with a single command. This accelerates development and deployment workflows.
The docker-compose.yml file that orchestrates a MySQL container:
We can map a container volume folder to a host folder:
Or we can just persist in the container without bind to a host folder (anonymous volume):
Also, we can use a :delegated flag that tells Docker to delegate management of this volume mount to the container runtime rather than Docker itself. This allows some performance optimizations.
To run the containers described in the docker-compose.yml file in the background (flag -d):
docker compose up -d
To stop running the containers described in the docker-compose.yml file:
docker compose stop
To destroy the containers described in the docker-compose.yml file:
docker compose down
Even if emulated, some x86 images have compatibility issues running on the Arm-based Apple silicon because they contain x86 instructions that cannot be executed on Arm. This depends on the specific app behavior.
The ecosystem is still catching up in terms of providing images with arm64 variants.
A temporary solution is using the platform key in docker-compose.yml, as it allows you to define alternative arm64 compatible images specifically for M1 devices. This avoids emulation and architecture issues.
In summary, Docker revolutionized the development and deployment of applications by introducing a standardized and portable way of packaging environments. Features like Dockerfile, Docker Compose and Docker Hub accelerate workflows and promote reuse, collaboration and consistency.