DEV Community

vigneshgs271096
vigneshgs271096

Posted on Edited on

Quick Learn Docker

Why Docker?
Docker is a tool that packages the application we develop along with the dependencies it needs. Why do we package it like this? Software needs dependent libraries, tools, settings, and runtimes. To run the software on another machine, we need the same versions of the dependencies as those used when we create it.

**Overall Concepts to Know while working with Docker

Dockerfile -> Docker image -> Container**

A Dockerfile is a saved, shareable instruction file. We use the Docker CLI to send this file to the Docker Daemon. The Daemon reads the instructions and builds a Docker Image (the packaged software/template). We can then use the CLI to tell the Daemon to run multiple Containers (the live applications) from that single Image.

If we need to start many containers and the containers have to communicate with each other for many practical uses. To define containers with details to start, stop, and manage the container lifecycle ( Orchestration ) and communicate with each other ( Networking ), we use a docker-compose file.

VM vs Docker

Each virtual machine displayed above acts like a completely different computer inside real computers.

Virtual machine architecture starts with the hypervisor, which takes part( abstracts and allocates, or we can call it partitions) of the machine's hardware and prepares virtual hardware. A guest OS runs inside each VM to run applications.

Unlike VMs, Docker containers don’t need a heavy Guest OS. Instead, they share the Host OS Kernel. By using Linux features like Namespaces, Docker creates an isolated environment (specific folders, networks, and files) where the process thinks it is running alone. Because Linux containers require a Linux kernel, running them on Windows or Mac requires Docker Desktop to spin up a single, lightweight Linux VM in the background, and all containers share that VM's kernel.

When we start a container, the Docker daemon starts a standard process but wraps it in two Linux features: Namespaces to isolate what the process can see, and cgroups to limit the CPU and RAM it can use. Because of this namespace isolation, the process has two identities: a standard PID assigned by the host OS, and an isolated PID (usually PID 1) inside the container's own namespace.

Commands

1. docker run –– name container1 ubuntu

The docker run ubuntu command tells Docker to run the latest version of the Ubuntu image. Docker first searches its local image cache on your machine. If the image is not found there, it reaches out to a Docker Registry (like Docker Hub), downloads the image to your cache, and then runs it as a container. If we didn’t mention its name by flag (–name < our name>), Docker assigns some random name to the container.

docker run –– name container1 -d ubuntu

If we start the container, the terminal is active, and logs are printed from the container. To use the same terminal for various uses, use the detach flag.

docker run –– name container1 ubuntu:4.0

We can specify a version instead of the latest version.

docker run –– name container1 -p 80:5000 ubuntu:4.0

We map host port 80 to the container’s app port 5000. From the outside world, we can communicate through the host port 80.

docker run –– name my-db -v /opt/datadir:/var/lib/mysql mysql

Containers are temporary and disposable. So if we store the DB inside the container, it is not recoverable. We can map the host folder to the container folder.
**
Container Folder = /var/lib/mysql

Host Folder = /opt/datadir**

docker run -e APP_COLOUR=blue kodekloud/simple-webapp

It is an environment variable, APP_COLOUR; we can use it in our container

docker ps

We can see all the active containers.
**
docker ps -a**

We can see all the containers, including inactive ones

docker start container1

Start the container

docker stop container1

Stops the container

docker rm container1

Deletes the container

docker images

Lists all the images

docker rmi nginx

Removes the image

docker pull ubuntu

Just pulls the image from Docker Hub, without running it.

docker exec -it container1 sh

It opens the container ubuntu, execute the command sh.

-i (Interactive): Keeps the input stream (STDIN) open so you can type.
-t (TTY): Allocates a fake terminal screen so it looks and acts like a normal command prompt. Together (-it), they give you an interactive shell.

docker inspect container1

Details about the container

docker logs container1

Prints out what the application has printed.

Example Dockerfile

FROM ubuntu:latest

# Update OS and install Python
RUN apt-get update && apt-get install -y python3 python3-pip

# Install the exact Flask version needed
RUN pip3 install Flask==2.0.1

# Set the folder and copy your laptop's code into the image
WORKDIR /opt
COPY . /opt

# The command to run when the container starts
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0"]
Enter fullscreen mode Exit fullscreen mode

The Dockerfile instructs the Docker daemon to start from the Ubuntu base image, and within that environment, we use RUN to install packages. We use WORKDIR to tell Docker to create a folder at /opt/home and execute all subsequent commands from that directory. We then copy the code from our host directory directly into that workspace. Finally, when we start the container, it executes the command provided inside the CMD array.

CMD is a flexible suggestion; the user can override CMD, as explained in the image.

docker run my-app bash
Enter fullscreen mode Exit fullscreen mode

Here, when running the container, by default it has to run python3 -m flask –host=0.0.0.0, but we override it with bash. When the container is started, it won’t start the Flask app; it will start bash. So we can use an ENTRYPOINT array, which prevents the command from being overridden. We can use a combination of them to our advantage.

# Lock in the main program 
ENTRYPOINT ["python3", "app.py"] 
# Provide a default argument 
CMD ["--port=8080"]
Enter fullscreen mode Exit fullscreen mode

In the above example, the runtime command is fixed as python3 app.py, but the port can be overridden.

Docker has a layered architecture; instructions that add or change files (RUN, COPY, ADD) create actual filesystem layers. Commands like ENTRYPOINT, CMD, or ENV just add lightweight metadata to the configuration, not physical layers

Layer 1 (Base Ubuntu Layer): The heavy foundation.
Layers 2 & 3 (apt and pip packages): The installed tools stacked on top.
Layer 4 (Source Code): Your actual application files.
The metadata telling Docker how to behave (Entrypoint): The final startup instructions.

Enter fullscreen mode Exit fullscreen mode

Once the image is built, the underlying layers of the image are already created and read-only. The Docker daemon has a cache of every layer and can reuse it when rebuilding the image. For example, if you alter the source code ( 4th layer ), Docker won’t start rebuilding from scratch from Layer1. It will start from the 4th; it can recognise that the top 3 layers are unchanged, and it has the cache of the layers to reuse. So it will build layer 4 and the metadata step.

On top of this, there is a temporary, read-and-write container layer above all read-only stable image layers. It is like placing a glass board on top of the book; we can scribble anything on top of the glass board. It creates an illusion of changing the book, but in reality, the book is static. If the container is deleted, this layer’s memory is lost. This is the reason it is termed temporary. It uses Copy-on-Write to create an illusion of changing files in the container layer. Please research it separately if you want more detail, but this abstraction is enough to work with Docker.

To save permanently on the host file system, use volumes, which were discussed above.

docker run -d -v ./Downloads/db:/var/lib/mysql mysql

If we are building a simple API for an online store, we create two microservices: a database and a backend process containing the business logic. If a customer requests to see products, the backend queries the database and returns the result to the client. Without Docker, these processes use a socket address (IP + PORT) to establish a connection. In Docker, we use a network abstraction.

By default, the Docker daemon acts as a router/gateway. It assigns a dynamic IP to each container and places them in the default bridge network. These dynamic IPs can be used for communication, but they change every time a container restarts.

To overcome this, the Docker daemon has an embedded DNS that resolves container names into IPs, allowing containers to communicate using just their names. However, this DNS feature does not work on the default bridge network. To use it, we must create a user-defined network and place both of our containers inside it.

We can bind a port on the host machine to the process inside the container. This is called port binding. It opens a doorway so the external world can communicate with the container, allowing users to access the API or web server running inside it.

docker network create my-store-network

# Start the database and put it on the network
docker run -d --name my-database --network my-store-network mysql

# Start the web server and put it on the exact same network
docker run -d --name my-web-server --network my-store-network my-python-app

Enter fullscreen mode Exit fullscreen mode

OLD WAY without Docker

# The web app connects to the database installed directly on your laptop
db_connection = mysql.connect(
    host="localhost",
    user="admin",
    password="my-secret-pw",
    port=3306
)

Enter fullscreen mode Exit fullscreen mode

Docker way

# The web app connects to the separate database container
db_connection = mysql.connect(
    host="my-database", # <--- Notice this change!
    user="admin",
    password="my-secret-pw",
    port=3306
)

Enter fullscreen mode Exit fullscreen mode

We have already discussed the volume above

#Create volume
docker volume create data_volume

# Use the volume
docker run -v data_volume:/var/lib/mysql mysql

Enter fullscreen mode Exit fullscreen mode

Data_volume is a permanent folder on the host. /var/lib/mysql is a folder in the container.

There are two main ways to persist data in Docker. Volumes are entirely managed by the Docker daemon and stored in a hidden area on the host. Bind Mounts allow you to map a specific, known folder on your host machine directly into the container(-v /opt/datadir:/var/lib/mysql). With a bind mount, Docker doesn't copy or sync files; it simply allows the container to read and write directly to that exact host folder in real-time. The modern way to attach either of these in the CLI is by using the explicit --mount flag.

Docker compose

In the example image, we need 5 containers. A Python app for people to vote and update an in-memory Redis DB; from this in-memory DB, the .NET worker gets the data and updates the actual Postgres DB. The Postgres app was fetched to show the result in the Node.js result app. This single file helps to orchestrate multiple containers.


services:
  vote:
    image: dockersamples/examplevotingapp_vote
    ports:
      - "5000:80"
    networks:
      - front1

  redis:
    image: redis:alpine
    networks:
      - front1
      - back1

  worker:
    image: dockersamples/examplevotingapp_worker
    networks:
      - back1
      - back2

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_PASSWORD=postgres
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - back2
      - front2

  result:
    image: dockersamples/examplevotingapp_result
    ports:
      - "5001:80"
    networks:
      - front2

volumes:
  db-data:

networks:
  front1:
  front2:
  back1:
  back2:

Enter fullscreen mode Exit fullscreen mode

These are some commands for a Dockerfile

docker compose up -d

#Start all the containers without a live terminal

docker compose ps

#List of all containers

docker compose logs -f vote

#Logs of the container from the vote app

docker compose down

#Stop all the containers

docker compose up -d --build

#Rebuild all the containers

Enter fullscreen mode Exit fullscreen mode

Docker Hub is a popular public container registry used widely for open-source images. For enterprise security, organizations usually host their own private registries (like AWS ECR or a self-hosted Docker Registry). We push our built images to these registries, and our servers pull those images to run them as containers.

docker login registry.mycompany.com

docker tag my-api:latest username/my-api:v1.0

docker push username/my-api:v1.0

docker pull username/my-api:v1.0 

Enter fullscreen mode Exit fullscreen mode

Top comments (0)