Skip to main content

Command Palette

Search for a command to run...

Docker

Updated
27 min readView as Markdown
V

Hi, I'm vaseem akram, a dedicated and passionate DevOps Engineer with extensive experience in automation, continuous integration, and infrastructure management.

How things work in the industry:

Let's consider the following scenario:

  • You get assigned a ticket to implement a login functionality. One developer works on this feature, creates the code, commits it, and pushes it to GitHub.

  • The developer then tells the DevOps engineer to deploy this code.

  • The DevOps engineer goes to the appropriate environment, clones the code from GitHub, and asks the developer what the procedure is for running the code.

  • The developer suggests checking the README.md for the setup instructions. The DevOps engineer follows the steps from the README.md, but ends up getting an error, like "Java not found."

  • The DevOps engineer then complains, saying it’s not working, and the developer responds, "It’s working on my local machine, but not in production."

This kind of issue arises because the local development environment and the production environment often don’t match, causing discrepancies like missing software versions or dependencies. The solution to this is containers.

Why do we need containers?

Containers help solve these issues by bundling all the required code and software dependencies into a single unit that can run consistently across different environments, without any mismatch. This container can be created using Docker.

The history of Docker and why it’s useful:

A few years ago, the common approach for running applications on multiple systems was using Virtual Machines (VMs).

In a VM, you have an operating system (like Linux) that runs on a host system. Under this operating system, you have the kernel, and under the kernel, there are resources like RAM, ROM, and networking. This operating system manages all resources and can run multiple virtual machines using a hypervisor.

You can run different operating systems (Windows, Linux, Mac) on top of this host OS using a hypervisor. For example, you could allocate 4GB of RAM to a virtual machine running Windows, while the host system might have 8GB of RAM.

However, this approach has some inefficiencies. Virtual machines are heavy, requiring significant resources, and each VM needs its own operating system and kernel.

Docker as a solution:

Docker is an alternative that helps solve these issues.

Just like a VM has an OS under the hood, Docker operates on top of the host operating system. However, in Docker, you don’t need to allocate resources for a full OS. Instead, you use the Docker Engine to run containers.

In Docker, containers share the host OS kernel, meaning you don’t have the overhead of running an entire OS in each container. Containers are lightweight because they only include what’s necessary to run an application (without duplicating the entire OS).

This makes Docker much more efficient than VMs. With Docker, you can run more containers on the same system, which is not only more resource-efficient but also allows for faster application deployment.

Docker Engine:

The Docker Engine consists of two key components:

  1. Docker CLI: The Command-Line Interface, which runs commands to interact with Docker.

  2. Docker Daemon (dockerd): The background process that manages Docker containers and images.

Docker daemon (dockerd) interacts with containerd, which is an open-source container management tool. Containerd provides the necessary tools to create, manage, and run containers.

When you run Docker commands from the CLI, they are passed to the Docker daemon (dockerd), which in turn communicates with containerd to manage the containers.

Docker Components:

  1. Dockerfile: A text file that contains instructions for building a Docker image.

  2. Docker Image: A snapshot of a filesystem and application code that can be executed in a container.

  3. Docker Container: A running instance of a Docker image.

The process flows as follows:

  • A Dockerfile is used to create a Docker Image.

  • The Docker Image is used to run a Docker Container.

Containerd is the underlying tool that Docker uses to manage and interact with containers, allowing you to create, destroy, and manage containers.

Docker Overview and Setup

Docker allows you to package applications and all their dependencies into a standardized unit called a container. Containers are lightweight, portable, and run consistently across various environments.

  • Installing Docker on Ubuntu:
    You used the following commands to install Docker:

    • sudo apt-get update updates the package manager's local repository index.

    • sudo apt-get install docker.io -y installs Docker.

    • systemctl status docker checks if the Docker service is running.

After installing Docker, you may face a permission denied issue when trying to use Docker commands as a regular user. This happens because Docker commands require root access or membership in the docker group.
To resolve this, you added the user to the Docker group:

    bashCopyEditsudo usermod -aG docker <username>
    sudo reboot

Creating Containers

Once Docker is set up and you've resolved the permission issue, you can start creating containers from Docker images. Docker images are like blueprints for containers.

Example 1: Running a MySQL Container

You used the following command to run a MySQL container:

bashCopyEditdocker run -e MYSQL_ROOT_PASSWORD=test@123 mysql:5.7

Explanation:

  • docker run: Creates and starts a container from a specified image.

  • -e MYSQL_ROOT_PASSWORD=test@123: Sets an environment variable MYSQL_ROOT_PASSWORD to configure the MySQL root password.

  • mysql:5.7: Specifies the image to use (in this case, MySQL version 5.7).

After the container is running, you can access it with:

bashCopyEditdocker exec -it <container_id> bash
mysql -u root -p

This starts a MySQL client inside the running container.

Building Docker Images

A Dockerfile is a script that contains a series of instructions to create a Docker image. Here's how you can create a Java application image:

Example: Building a Java Application Docker Image

dockerfileCopyEdit# Get a base image with JDK installed
FROM openjdk:11

# Set the working directory to /app
WORKDIR /app

# Copy the Java code into the container
COPY Hello.java .

# Compile the Java code
RUN javac Hello.java

# Run the compiled Java code
CMD ["java", "Hello"]

Dockerfile Instructions

  1. FROM
    The FROM instruction specifies the base image for your Docker image. Every Dockerfile starts with the FROM instruction.

    Theory:
    It sets the base image for the Docker image.
    You can use official images from Docker Hub or custom images.
    Example:

     DockerfileCopyEdit# Use an official Node.js runtime as the base image
     FROM node:14
    

    Here, we are using the official Node image tagged 14 as the base image.

  2. LABEL
    The LABEL instruction adds metadata to an image, like the maintainer’s name, version, description, etc.

    Theory:
    It helps with adding key-value pairs of metadata that can describe the image.
    Example:

     DockerfileCopyEditLABEL maintainer="your-email@example.com"
     LABEL version="1.0"
     LABEL description="This is a simple Node.js application"
    

    Here, the LABEL instruction adds metadata to the Docker image.

  3. RUN
    The RUN instruction executes a command inside the Docker image during the image build process. It can be used to install software or make modifications to the image.

    Theory:
    RUN is often used to install software packages or execute shell commands during the build process.
    Example:

     DockerfileCopyEdit# Install dependencies in the container
     RUN apt-get update && apt-get install -y curl
    

    This example updates the package list and installs curl inside the image.

  4. CMD
    The CMD instruction provides the default command that gets executed when a container is started from the image. If a command is passed while running the container, it overrides the CMD instruction.

    Theory:
    CMD defines the default executable for the container. You can use either the exec form (CMD ["executable", "param1", "param2"]) or the shell form (CMD ["param1", "param2"]).
    Example:

     DockerfileCopyEditCMD ["node", "app.js"]
    

    This will run node app.js when the container starts. If you specify a command while running the container, this command will be overridden.

  5. ENTRYPOINT
    The ENTRYPOINT instruction sets the main command for the container to execute. Unlike CMD, which can be overridden when running a container, ENTRYPOINT cannot be overridden unless specified explicitly with --entrypoint.

    Theory:
    The ENTRYPOINT defines the primary command to be executed.
    It is typically used when you always want the container to run the same command.
    Example:

     DockerfileCopyEditENTRYPOINT ["python3", "app.py"]
    

    This makes sure that whenever the container is started, python3 app.py is executed.

  6. COPY
    The COPY instruction copies files from the host system into the container’s file system.

    Theory:
    COPY is used to copy files or directories from the build context to the container. It’s simpler and more efficient than ADD if you're just copying files.
    Example:

     DockerfileCopyEditCOPY . /app
    

    This copies all files from the current directory on the host to the /app directory inside the container.

  7. ADD
    The ADD instruction is similar to COPY but with some additional features, such as automatically extracting tar archives or downloading remote files.

    Theory:
    ADD can fetch files from remote URLs and untar archives. COPY cannot do this.
    Example:

     DockerfileCopyEditADD https://example.com/file.tar.gz /app
    

    This will download the file from the URL and add it to the /app directory inside the container.

  8. WORKDIR
    The WORKDIR instruction sets the working directory inside the container for any RUN, CMD, ENTRYPOINT, COPY, and ADD instructions that follow.

    Theory:
    Sets the working directory for commands. If the directory doesn't exist, it’s created.
    Example:

     DockerfileCopyEditWORKDIR /app
    

    This sets /app as the working directory for subsequent commands.

  9. ENV
    The ENV instruction sets environment variables that can be used inside the container.

    Theory:
    Environment variables are often used for configuration or to pass data inside the container.
    Example:

     DockerfileCopyEditENV NODE_ENV=production
    

    This sets the NODE_ENV environment variable to production.

  10. EXPOSE
    The EXPOSE instruction informs Docker that the container will listen on the specified network ports at runtime.

    Theory:
    It does not publish the port but serves as documentation to let the user know which ports the container will use.
    Example:

    DockerfileCopyEditEXPOSE 8080
    

    This informs Docker that the container will listen on port 8080.

  11. VOLUME
    The VOLUME instruction creates a mount point with a specified path inside the container for persistent storage.

    Theory:
    Volumes are used to persist data and share it between containers.
    Data inside a volume remains even if the container is stopped or removed.
    Example:

    DockerfileCopyEditVOLUME /data
    

    This creates a volume at /data inside the container.

  12. USER
    The USER instruction sets the user name or UID (and optionally the group name or GID) to use when running the container.

    Theory:
    It’s a good practice to run containers as a non-root user for security reasons.
    Example:

    DockerfileCopyEditUSER node
    

    This makes the node user the default user inside the container.

  13. ARG
    The ARG instruction defines a build-time variable that can be passed to the Docker build process.

    Theory:
    ARG allows for parameterizing the Dockerfile with variables that can be set during the build.
    Example:

    DockerfileCopyEditARG VERSION=1.0
    

    This defines a build argument VERSION with a default value of 1.0. You can override this value at build time like so:

    bashCopyEditdocker build --build-arg VERSION=2.0 .
    
  14. STOPSIGNAL
    The STOPSIGNAL instruction sets the signal that Docker should send to stop the container.

    Theory:
    It allows you to specify a custom stop signal other than the default SIGTERM.
    Example:

    DockerfileCopyEditSTOPSIGNAL SIGINT
    

    This will make Docker use SIGINT instead of SIGTERM when stopping the container.

  15. SHELL
    The SHELL instruction allows you to specify the default shell to use when running commands in the Dockerfile.

    Theory:
    By default, Docker uses /bin/sh -c, but you can override it with this instruction.
    Example:

    DockerfileCopyEditSHELL ["/bin/bash", "-c"]
    

    This changes the default shell to /bin/bash.

  16. HEALTHCHECK
    The HEALTHCHECK instruction tells Docker how to test if a container is healthy or not. It can help Docker manage containers by automatically restarting unhealthy containers.

    Theory:
    It runs a command inside the container to determine if the container is healthy.
    Example:

    DockerfileCopyEditHEALTHCHECK CMD curl --fail http://localhost:8080 || exit 1
    

    This checks if the service inside the container on port 8080 is responding.


Example Dockerfile:

Here’s a simple example using several instructions:

DockerfileCopyEdit# Step 1: Use Node.js base image
FROM node:14

# Step 2: Set working directory
WORKDIR /app

# Step 3: Copy the package.json file
COPY package.json .

# Step 4: Install dependencies
RUN npm install

# Step 5: Copy the rest of the application files
COPY . .

# Step 6: Expose the port the app will run on
EXPOSE 3000

# Step 7: Set environment variable
ENV NODE_ENV=production

# Step 8: Define the default command
CMD ["npm", "start"]

This Dockerfile builds an image for a Node.js application, setting up the environment, installing dependencies, and defining a default command to start the app.

You can build the Docker image using:

bashCopyEditdocker build -t java_app:latest .

Running Containers from Images

To run a container from the built image:

bashCopyEditdocker run java_app:latest

Docker Volumes (Simplified)

Docker volumes are used to persist data generated by and used by Docker containers. They allow data to survive container restarts and removal, and also enable data sharing between containers. Volumes are stored outside the container's filesystem, making them ideal for data that needs to persist even after containers are stopped or deleted.

Types of Docker Volumes

  1. Named Volumes

  2. Anonymous Volumes

  3. Host Volumes (Bind Mounts)

1. Named Volumes

  • What are Named Volumes?

    • Named volumes are Docker-managed volumes that have a specific name and are stored in Docker's default volume directory (/var/lib/docker/volumes on Linux).

    • Named volumes are useful when you need persistent storage that can be easily referenced by container names.

  • Example:

      bashCopyEdit# Create a named volume
      docker volume create my-volume
    
      # Run a container and mount the named volume
      docker run -d -v my-volume:/data --name my-container my-image
    

    Here, a named volume my-volume is created and mounted to /data inside the container. Data written to /data will persist in the volume, even after the container is removed.

  • Use case:
    Useful for database containers where data needs to persist between container restarts.

2. Anonymous Volumes

  • What are Anonymous Volumes?

    • Anonymous volumes are automatically created by Docker when you mount a volume but do not specify a name.

    • These volumes are less manageable compared to named volumes since they don't have a name, but they still allow data persistence.

  • Example:

      bashCopyEdit# Run a container with an anonymous volume
      docker run -d -v /data --name my-container my-image
    

    Here, Docker creates an anonymous volume and mounts it to /data inside the container.

  • Use case: Often used when you want Docker to manage the volume without needing to name it. Typically used for temporary storage.

3. Host Volumes (Bind Mounts)

  • What are Host Volumes?

    • Host volumes (or bind mounts) allow you to mount a directory or file from the host system directly into the container.

    • Unlike named volumes, host volumes are tied directly to the file system on the host machine.

    • This is useful for accessing host system data directly, such as configuration files or logs.

  • Example:

      bashCopyEdit# Run a container and mount a host directory to the container
      docker run -d -v /path/on/host:/path/in/container --name my-container my-image
    

    In this example, /path/on/host is a directory on your host system, and /path/in/container is where it will be mounted inside the container. Any changes to the mounted folder will be reflected on both the host and the container.

  • Use case: Ideal for sharing source code or logs between the host system and the container, or when you need to access host-specific files inside a container.

Example: Persisting MySQL Data

Without volumes, data inside a container is ephemeral. To persist data, you can create a volume and mount it inside the container:

docker run -d -e MYSQL_ROOT_PASSWORD=root -v /path/to/volume:/var/lib/mysql --name mysql-safe mysql:latest
  • -v /path/to/volume:/var/lib/mysql: Mounts a host directory (/path/to/volume) into the container (/var/lib/mysql). This ensures that MySQL's data is persisted on the host system.

Docker Networks

Docker networks allow containers to communicate with each other and with the external world. By default, Docker creates a "bridge" network, which is used by containers unless specified otherwise. Docker provides several types of networks, each with its specific use cases.

Types of Docker Networks

  1. Bridge Network

    • Default Network: If no network is specified when you run a container, Docker uses the bridge network.

    • Use Case: It's typically used for containers that need to communicate on the same host.

    • Example: Useful for small applications or multi-container apps that need to communicate internally.

  2. Host Network

    • Host’s Networking: In this mode, the container shares the network stack of the host. The container doesn’t get its own IP address but uses the host's IP.

    • Use Case: Best when performance is crucial and when containers need direct access to the host’s networking (e.g., network monitoring tools or apps requiring high network performance).

  3. None Network

    • No Network: In this mode, the container does not have access to any network. It’s isolated from the host network.

    • Use Case: Used when a container should not be able to access any network (e.g., for containers that need no network connectivity or for strict security reasons).

  4. Overlay Network

    • Multi-Host Networking: This network type is used when containers are deployed across multiple Docker hosts (typically in a Docker Swarm or Kubernetes setup). It creates a virtual network that spans across multiple hosts.

    • Use Case: Best suited for multi-host or multi-node deployments in distributed systems.

  5. Macvlan Network

    • Direct MAC Address: This allows containers to appear as physical devices on the network with their own MAC addresses.

    • Use Case: Useful when containers need to be directly accessible by external network devices (e.g., legacy applications that require direct network access).

  6. Custom User-Defined Network

    • User-Defined Bridge Networks: This type of network provides more advanced features such as automatic DNS resolution for containers, allowing containers on the same network to refer to each other by container names.

    • Use Case: This is a good option for communication between containers in applications that require direct and reliable communication.


Explanation of Docker Network Commands in the Example

In the given Dockerfile and usage context, various Docker network commands are relevant:

1. docker network create

This command is used to create a custom Docker network.

Example:

bashCopyEditdocker network create --driver bridge my-network

Usage:

  • --driver bridge: Specifies the type of network driver. In this case, bridge means the containers will communicate on the same host.

  • my-network: The name of the network being created.

Purpose in the Example:

  • The docker network create command is useful when you need a custom network for isolated communication between containers, such as in multi-container applications.

2. docker run --network

The docker run command is used to start a new container. The --network flag allows you to specify the network the container should join.

Example:

bashCopyEditdocker run --network my-network --name my-container my-image

Usage:

  • --network my-network: This flag tells Docker to connect the container to my-network (the user-defined bridge network).

  • --name my-container: Names the container for easier reference.

  • my-image: The image from which the container will be created.

Purpose in the Example:

  • This command ensures that the container runs in the specified custom network (my-network), allowing containers in the same network to communicate with each other.

3. docker inspect

This command is used to retrieve detailed information about a Docker container or network.

Example:

bashCopyEditdocker inspect my-network

Usage:

  • my-network: The name of the network whose information you want to inspect.

Purpose in the Example:

  • This command helps you view the network configuration, including the connected containers, network settings, IP ranges, etc. It’s useful for troubleshooting and ensuring that the containers are correctly connected to the intended network.

4. docker network ls

This command lists all the Docker networks on the system.

Example:

bashCopyEditdocker network ls

Usage:

  • Lists the names and types of all networks created in Docker.

Purpose in the Example:

  • This helps you check the available networks, whether the custom network my-network is created and available for use.

5. docker network connect

This command allows you to connect an existing container to a network after it’s already running.

Example:

bashCopyEditdocker network connect my-network my-container

Usage:

  • my-network: The network to which the container should be connected.

  • my-container: The running container that should be connected to the network.

Purpose in the Example:

  • If a container was not started with the --network option but you want it to join a network afterward, this command adds it to the specified network.

6. docker network disconnect

This command disconnects a container from a network.

Example:

bashCopyEditdocker network disconnect my-network my-container

Usage:

  • Disconnects my-container from my-network.

Purpose in the Example:

  • This command can be useful if you want to isolate a container from a network or change its network configuration.

Conclusion

Docker networks provide a way to control how containers communicate. Using the appropriate network type based on your application’s requirements can improve performance, security, and manageability.

In the above commands, the --network option is crucial for ensuring containers are part of the desired network for communication. Additionally, using docker network create, docker network connect, and docker network inspect gives you full control over the networking environment of your Dockerized applications.

Example: Connecting Flask and MySQL Containers

  1. Create a network:

     docker network create twotier
    
  2. Run MySQL and Flask containers on this network:

     docker run -d --network twotier --name mysql -e MYSQL_ROOT_PASSWORD=root mysql:5.7
     docker run -d --network twotier --name flask -e MYSQL_HOST=mysql -e MYSQL_PASSWORD=root flask-image
    

What is Docker Compose?

Docker Compose is a tool that allows you to define and manage multi-container Docker applications. With Docker Compose, you can define all your services (containers), networks, and volumes in a single YAML file (docker-compose.yml). This simplifies the process of running multiple containers together, such as a frontend and backend application or a database with its application.

Docker Compose is especially useful for applications that need to interact with multiple services (like databases, caches, etc.), as it lets you define and orchestrate all the services in a single command.

Explanation of the Example Docker Compose File

Here's the breakdown of the provided docker-compose.yml example for a two-tier application (backend and MySQL database):

yamlCopyEditversion: "3"
services:
  backend:
    build: .
    ports:
      - "5000:5000"
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: test@123
      MYSQL_DB: KYC
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: test@123
      MYSQL_DATABASE: KYC
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:

Key Sections:

  1. version: "3"

    • Specifies the version of the Docker Compose file format.

    • Version 3 is the most commonly used format for production setups.

  2. services:

    • This section defines the containers or services that make up the application. Here, we have two services: backend and mysql.
backend service:
  • build: .

    • Specifies that the backend container will be built from the Dockerfile in the current directory (.).
  • ports:

    • "5000:5000": Maps port 5000 on the host to port 5000 on the container. This allows you to access the backend application at localhost:5000 (if running on your local machine).
  • environment:

    • Defines environment variables that are passed into the container. These variables are used to configure the backend application to connect to the MySQL database.

    • MYSQL_HOST refers to the MySQL service (because it's defined as mysql in Docker Compose).

    • MYSQL_USER, MYSQL_PASSWORD, and MYSQL_DB are used to specify credentials and the database name for the connection.

  • depends_on:

    • Ensures that the backend service will not start until the mysql service is up and running. This is important for ensuring the backend doesn't try to connect to the database before it's ready.
mysql service:
  • image: mysql:5.7

    • Specifies the MySQL Docker image to use, in this case, version 5.7.
  • ports:

    • "3306:3306": Maps port 3306 on the host to port 3306 on the MySQL container, making the database accessible from the host system on port 3306.
  • environment:

    • Defines the root password and the name of the database (KYC) for MySQL. These variables are used by the MySQL image to set up the database during container startup.
  • volumes:

    • - mysql-data:/var/lib/mysql: This is a named volume (defined later) that stores MySQL data. It persists data outside the container so that even if the container is removed, the data remains intact. The volume is mounted to the MySQL data directory (/var/lib/mysql), where MySQL stores its databases.
  1. volumes:

    • This section defines named volumes that Docker will manage. In this case, the volume mysql-data is used to persist MySQL data across container restarts and removals.

What We Used and Why:

  1. services:

    • Used to define the containers (backend and mysql) that make up the multi-container application. This is where you specify container details like images, build contexts, environment variables, ports, etc.
  2. build: .:

    • Used for the backend service to build the Docker image from the Dockerfile in the current directory. This is useful for custom applications that need to be built from source.
  3. ports:

    • Used to expose container ports to the host system. This is crucial for enabling communication with the container. For example, the backend is accessible via localhost:5000, and the MySQL database is accessible via port 3306.
  4. environment:

    • Sets environment variables that configure the behavior of the services. For example, the backend needs to know how to connect to MySQL, so it gets the database credentials through environment variables.
  5. depends_on:

    • Ensures that the backend container waits for the mysql container to be ready before starting. This is important to avoid the backend trying to connect to MySQL before it's up and running.
  6. volumes:

    • Volumes are used to persist data. The mysql-data volume ensures that the database data is stored outside the MySQL container and survives container restarts and removal. It keeps your data safe and persistent.

Running the Services:

To start the multi-container application, use this command:

bashCopyEditdocker-compose up -d
  • -d stands for "detached mode," which means the containers will run in the background.

This will build the containers (if needed), start the MySQL database and the backend application, and connect them based on the configuration you provided in the docker-compose.yml file.

Summary:

Docker Compose simplifies managing multi-container applications by defining services, networks, and volumes in a single YAML file. In the above example:

  • We have a backend service and a MySQL database service.

  • The backend waits for MySQL to start, and the data from MySQL is persisted using a named volume (mysql-data).

  • Ports are mapped to allow communication between the host and the containers.

What is a Multistage Dockerfile?

A multistage Dockerfile is a Dockerfile that uses multiple FROM instructions to define different stages in the build process. Each stage can use a different base image, and you can selectively copy files from one stage to another. This allows you to keep the final Docker image small and clean by excluding unnecessary dependencies that were only needed during the build phase.

The key benefit of multistage builds is that they allow you to separate the build environment (which may require many tools and dependencies) from the runtime environment (which only needs the final application and its runtime dependencies). By doing this, you can create a smaller, more efficient Docker image for deploying your application.

Why Do We Use Multistage Dockerfiles?

  1. Smaller Final Image: By separating build and runtime stages, you can avoid including unnecessary tools, libraries, and dependencies in the final image, reducing its size.

  2. Cleaner and More Secure: Only the essential files and dependencies are included in the final image, reducing potential security risks.

  3. Improved Build Process: Multistage builds can make your Dockerfile easier to maintain and more modular, especially for complex applications that need different build environments.

  4. Efficiency: You don't need to keep the intermediate images around once they have served their purpose, making the process more efficient.

Example of a Multistage Dockerfile for a Flask Application

Let's break down the example provided:

dockerfileCopyEdit# Stage 1: Build stage
FROM python:3.9 AS backend-builder
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt

# Stage 2: Runtime stage
FROM python:3.9-slim
WORKDIR /app
COPY --from=backend-builder /usr/local/lib/python3.9/site-packages/ /usr/local/lib/python3.9/site-packages/
COPY --from=backend-builder /app /app
CMD ["python", "run.py"]

Explanation of Each Stage:

  1. Stage 1: Build Stage

    • FROM python:3.9 AS backend-builder: This defines the first stage using the python:3.9 image, which includes all the tools needed for building the application, such as pip and other dependencies.

    • WORKDIR /app: Sets the working directory to /app inside the container. All following commands will be executed in this directory.

    • COPY . .: Copies the entire current directory (the application code) into the /app directory inside the container.

    • RUN pip install -r requirements.txt: Installs all the dependencies listed in the requirements.txt file, which is typical for Python-based applications. This is done in the build stage, as this stage has all the necessary dependencies and tools.

  2. Stage 2: Runtime Stage

    • FROM python:3.9-slim: This defines the second stage using the python:3.9-slim image, which is a much smaller image compared to the first one because it doesn't include the build tools (like pip or other development dependencies).

    • WORKDIR /app: Sets the working directory to /app inside the container, similar to the first stage.

    • COPY --from=backend-builder /usr/local/lib/python3.9/site-packages/ /usr/local/lib/python3.9/site-packages/: This copies the installed dependencies (from the first stage) to the appropriate directory in the runtime image. We're using the --from=backend-builder option to copy files from the first stage (backend-builder).

    • COPY --from=backend-builder /app /app: This copies the application code from the first stage into the runtime stage.

    • CMD ["python", "run.py"]: Specifies the command to run when the container starts. In this case, it starts the Flask application using the run.py script.

Use Case and Why It Works:

Why Use a Multistage Dockerfile for a Flask Application?

  1. Build Stage (Stage 1):

    • In the first stage, we use a full Python image (python:3.9), which includes all the dependencies and tools required to install Python packages (pip), install the application's dependencies, and compile any required assets.

    • However, we don't want to carry over these build tools into the final image because they are unnecessary for running the Flask application.

  2. Runtime Stage (Stage 2):

    • In the second stage, we use a much smaller base image (python:3.9-slim). This image only includes the necessary runtime components for running Python applications, which drastically reduces the image size.

    • We copy over only the essential parts from the build stage (application code and installed dependencies) to the final image. This way, we avoid carrying over unnecessary files, such as build tools, caches, and other development dependencies.

Benefits in This Example:

  • Smaller Image Size: The final image is smaller because the build tools and dependencies are not included in the runtime image.

  • Security: By removing unnecessary build tools from the runtime image, you reduce the attack surface and make the image more secure.

  • Faster Deployment: With a smaller image, deployment is faster and uses fewer resources.

Summary:

A multistage Dockerfile allows you to optimize the Docker build process by separating the build environment from the runtime environment. In the provided example:

  • The build stage installs dependencies and prepares the application.

  • The runtime stage uses a smaller image, reducing the size of the final Docker image and only includes what is necessary to run the application.

This approach is highly beneficial when working with applications that require complex build dependencies, ensuring a clean, efficient, and secure final image.

Docker Security (Docker Scout)

Docker Scout is a tool that helps you identify vulnerabilities and potential risks in your Docker images by scanning them for known security issues, outdated dependencies, and other risks. Below are some examples of using Docker Scout and similar tools like Trivy for scanning Docker images.

1. Using Docker Scout to Scan an Image:

Docker Scout can be used to scan your images for security vulnerabilities. Here's a basic command to scan a Docker image:

bashCopyEditdocker scout quickscan <image-name>

Example:

bashCopyEditdocker scout quickscan myapp:latest

This command will scan the myapp:latest Docker image for known security vulnerabilities and provide a summary of the results.

2. Scan with Detailed Results:

To get more detailed information about the vulnerabilities detected in an image, use the --detail flag:

bashCopyEditdocker scout quickscan --detail <image-name>

Example:

bashCopyEditdocker scout quickscan --detail myapp:latest

This will give you a more in-depth report, including specifics about each vulnerability, such as severity, CVE (Common Vulnerabilities and Exposures) identifiers, and potential fixes.

3. Scan Local Docker Image (Local Image Scanning):

To scan a local image that has already been built (and is present in your local Docker environment), you can use:

bashCopyEditdocker scout quickscan <local-image-name>

Example:

bashCopyEditdocker scout quickscan my-local-image

4. Using Trivy to Scan Docker Images (Alternative to Docker Scout):

Trivy is another popular security scanning tool for Docker images. Here's how you can use Trivy to scan an image:

Install Trivy:

If you haven't installed Trivy yet, you can do so with:

bashCopyEditbrew install aquasecurity/trivy/trivy  # macOS
sudo apt install trivy                # Ubuntu

Scan a Docker Image Using Trivy:

bashCopyEdittrivy image <image-name>

Example:

bashCopyEdittrivy image myapp:latest

This will scan the myapp:latest image for vulnerabilities, and you'll get a detailed output including the vulnerabilities found, their severity levels, and possible remediation steps.

5. Scan Docker Image for Specific Vulnerability Types (Trivy Example):

You can also limit the scan to specific types of vulnerabilities like "severity", "config", etc., using the --severity option in Trivy:

bashCopyEdittrivy image --severity HIGH,CRITICAL <image-name>

Example:

bashCopyEdittrivy image --severity HIGH,CRITICAL myapp:latest

This will scan the image but only report vulnerabilities that are of HIGH or CRITICAL severity.

6. Scan a Specific Directory Using Trivy (For Local Dockerfiles/Contexts):

You can also scan the local Dockerfile or directory that contains your project files:

bashCopyEdittrivy fs <path-to-project-directory>

Example:

bashCopyEdittrivy fs /path/to/project

This command will scan your local directory for vulnerabilities related to files and configurations within it.

Summary of Common Commands:

  1. Docker Scout (Quick Scan):

     bashCopyEditdocker scout quickscan <image-name>
    
  2. Docker Scout (Detailed Scan):

     bashCopyEditdocker scout quickscan --detail <image-name>
    
  3. Trivy (Scan Docker Image):

     bashCopyEdittrivy image <image-name>
    
  4. Trivy (Scan with Severity Filter):

     bashCopyEdittrivy image --severity HIGH,CRITICAL <image-name>
    
  5. Trivy (Scan Local Files):

     bashCopyEdittrivy fs <path-to-project-directory>
    

Using these tools, you can identify vulnerabilities, outdated dependencies, and security risks in your Docker images, ensuring a more secure and stable environment for your application.

Summary

  • Docker allows you to build, run, and manage containers.

  • Dockerfile is a script that defines how to build a Docker image.

  • Volumes help to persist data outside of containers.

  • Networks allow containers to communicate with each other.

  • Docker Compose helps manage multi-container applications easily.

  • Multistage Dockerfiles help to create smaller and more efficient images by separating build and runtime stages.

  • Security tools like Docker Scout and Trivy help identify vulnerabilities in images.