Table of contents
- Problems before Docker:
- Enter Docker: The Game Changer!
- How Docker Solves the Problem:
- What is Docker:
- Docker prerequisite terminologies:
- Basic Docker commands:
- Examples :
- Run - tags
- Port Mapping:
- RUN - Volume Mapping:
- Inspect Container:
- Container Logs
- How to write a Dockerfile?
- Explanation of the Dockerfile:
- Pushing the image to Docker Hub:
- Pulling the Image from Docker Hub
- How Applications are Containerized in Docker:
- Resources:
Problems before Docker:
At a software company, a new developer named Jake joined the team. Fresh out of college and eager to make an impact, Jake was assigned to work on a project called Webify, a web application that the team had been maintaining for months.
On his first day, Jake asked the lead developer, Sara, how to set up the project. Sara shared the steps:
Install Nginx for serving the frontend.
Install Python 3.8 for running the backend.
Install PostgreSQL for the database.
Use VS Code for development.
Jake enthusiastically followed the instructions but opted to install the latest versions of everything, thinking it would be better. After hours of setup, he confidently ran the application, but instead of a working app, he was greeted by a screen full of errors!
Jake double-checked the steps, but the errors persisted. Frustrated, he went to Sara.
“Hey, Sara,” Jake said. “I followed your steps, but the app isn’t running. I think there’s a bug in the project.”
Sara frowned. “That’s odd. It works fine on my machine.”
Curious, Sara sat at Jake’s desk, ran the same commands, and found the errors. Then she returned to her own machine, ran the app, and it worked perfectly.
After some investigation, Sara discovered the issue:
Jake had installed Nginx 1.25, but the project required Nginx 1.20.
Jake had installed Python 3.12, but the project relied on features only available in Python 3.8.
Jake's PostgreSQL setup wasn’t configured the same way as Sara’s.
The project was breaking because the environments didn’t match.
Enter Docker: The Game Changer!
“Jake,” she said, “this isn’t your fault. Setting up environments manually is tricky and error-prone. That’s why we use Docker!”
Sara explained:
“Docker lets us package the app, along with all its dependencies—like specific versions of Nginx, Python, and PostgreSQL—into a container. These containers run the same everywhere, whether it’s my laptop, your laptop, or a server in production.”
How Docker Solves the Problem:
Consistency: Docker ensures everyone works in the same environment. Instead of installing software manually, Jake could just run a Docker container with the exact setup the app needed.
Simplicity: Jake didn’t need to worry about version mismatches anymore. Docker used a Dockerfile to specify all the required versions.
Portability: Once Jake’s app ran in Docker on his laptop, it would run the same way on Sara’s laptop or even in production.
Quick Onboarding: Jake could’ve been up and running in minutes, without wrestling with installations and configurations.
What is Docker:
Docker is a containerization platform that enables developers to package applications along with all their dependencies (libraries, tools, configurations) into lightweight, portable containers. These containers ensure that the application runs consistently across different environments, whether it's your local machine, a testing server, or a production server.
Docker prerequisite terminologies:
What is a Dockerfile?
A Dockerfile is like a recipe book that Docker uses to build an image. It’s a text file with step-by-step instructions for creating a Docker image.
The Base Image to Use: What foundation to start with (e.g., Python, Ubuntu).
What to Add: Your app’s code, libraries, and dependencies.
How to Set It Up: Commands to run, environment variables, and file locations.
What is an Image?
A Docker image is like a recipe for creating a container.
It’s a blueprint that tells Docker what to put in the container and how to set it up.
When you run an image, Docker uses it to create a container.
What is a Base Image?
A base image is the starting point for your Docker image. It’s like the foundation of a house:
A base image provides the operating system and basic tools your app needs.
You build on top of it by adding your app and its specific requirements.
What is a Container?
A container is like a portable, self-contained box for your application. Inside this box:
Your application runs.
It has everything the app needs to work (libraries, tools, settings).
Hmm, a bit tricky to understand, right? Let's make it clear with a simple example:
You’ve built a web app called Lend Manager in Go. It helps users manage loans and repayments. Now, you want to containerize the app to make it easier to:
Run it consistently across different environments (your laptop, servers, or your friend’s machine).
Share it with your friend so they don’t have to manually install Go or dependencies to run it.
1. Create the Dockerfile
You start by writing a Dockerfile
to define how your app should be containerized.
Here’s an example Dockerfile
for your Go app:
# Use an official Go image as the base
FROM golang:1.21
# Set the working directory inside the container
WORKDIR /app
# Copy the Go code into the container
COPY . .
# Build the Go app inside the container
RUN go build -o lend-manager
# Expose port 8080 for the app
EXPOSE 8080
# Command to run the app
CMD ["./lend-manager"]
Why Write This Dockerfile?
FROM: Starts with a pre-installed Go environment (no need to install Go manually on different machines).
WORKDIR: Sets up a clean workspace inside the container.
COPY: Copies your code into the container so it can be built.
RUN: Compiles your Go app into an executable (
lend-manager
).EXPOSE: Opens port
8080
to make the app accessible.CMD: Defines how to start the app (
./lend-manager
).
2. Build the Docker Image
Now, you build the Docker image using the Dockerfile.
Run this command in your terminal:
docker build -t lend-manager-app .
What happens: Docker follows the instructions in the Dockerfile and creates an image called
lend-manager-app
.This image contains the entire environment (OS, Go runtime, app code, etc.) needed to run your app.
3. Run the Docker Container
You run the app as a container from the image.
Run this command:
docker run -p 8080:8080 lend-manager-app
What happens:
Docker starts the app in a container.
The app is accessible on
http://localhost:8080
.
4. Share the App with Your Friend
You want your friend to easily run the app without setting up Go or manually building the code.
Share the Docker Image via a Registry
You can upload the image to Docker Hub or a private registry:
docker tag lend-manager-app your-dockerhub-username/lend-manager-app:latest docker push your-dockerhub-username/lend-manager-app:latest
Your friend can pull the image using:
docker pull your-dockerhub-username/lend-manager-app:latest docker run -p 8080:8080 your-dockerhub-username/lend-manager-app:latest
Step 1: Running the Image
When your friend pulls and runs the Docker image, this happens:
Pulling the Image
If the image isn’t already on your friend’s system, Docker will download it from the registry (e.g., Docker Hub).
The image contains:The app’s code.
All the dependencies (libraries, tools, etc.) needed for the app.
A pre-configured environment to run the app (e.g., the Go runtime).
Your friend doesn’t have to manually install or configure anything!
Step 2: Running the Container
When your friend runs the Docker container from the image:
The App Runs
The app starts inside an isolated environment (the container).
The app behaves exactly as it did on your system since the environment is the same.
The App is Accessible
By exposing port
8080
(as defined in your Dockerfile), the app becomes available onhttp://localhost:8080
.Your friend can open this URL in their browser and use the app.
Basic Docker commands:
Command | Description |
docker run <image_name> | Run a container from the specified image |
docker ps | List all running containers |
docker ps -a | List all containers (running and stopped) |
docker stop <container_name> | Stop a running container |
docker rm <container> | Remove a stopped container |
docker images | List all images available locally |
docker rmi <image> | Remove an image from the local system |
docker pull <image> | Download an image from a registry (like Docker Hub) |
docker run -d <image> | Run the container in detached mode (in the background) |
docker run -it <image> | Run the container in interactive mode (use terminal inside) |
docker exec <container_id> | Execute a command inside a running container |
Examples :
docker pull ubuntu:
Downloads the latest Ubuntu image from Docker Hub to your local system.
docker run ubuntu:
Creates and runs a container from the Ubuntu image interactively.
docker ps:
Lists all currently running containers.
docker ps -a:
Lists all containers, including stopped ones.
docker stop ubuntu:
Stops a running container named or with the ID
ubuntu
(use container name/ID, not image name).docker images:
Displays all downloaded Docker images on your system.
docker rm ecstatic_mendeleev:
Removes the stopped container named
ecstatic_mendeleev
.docker rmi ubuntu:
Deletes the Ubuntu image from your local system.
docker run -d ubuntu:
Runs an Ubuntu container in detached mode (in the background).
docker run -it ubuntu:
Runs an Ubuntu container interactively with a terminal session.
docker exec 929a9d568f88 cat /etc/*release*:
Executes the
cat /etc/*release*
command in the running container with ID929a9d568f88
to display its OS release information.
Run - tags
docker pull redis:4.0
Runs a container using a specific version of Redis image for instance version 4.0.
docker run -i
Runs a container in interactive mode, keeping STDIN open for input (commonly used with
-t
).docker run -it
Runs a container interactively with a terminal session (combines
-i
and-t
for interactive input and terminal emulation).
Port Mapping:
Port mapping in Docker connects a port on your host machine (your computer) to a port inside a running Docker container. It allows you to access applications running inside the container from outside the container.
Why it is Done:
Containers are isolated from your computer’s network by default. Port mapping makes services (e.g., web servers or databases) in a container accessible to the host machine or external users.
Imagine you a developer working on a new web app. You’ve decided to use NGINX as a web server, running it in a Docker container. Here’s how port mapping helps:
You create a container using the nginx image. By default, the web server listens on port
80
inside the container.But when you try to access
http://localhost
on your browser, nothing happens. Why? Because the container’s internal port80
isn’t connected to your computer’s network.You realize you need to map a port on your computer to the container’s port
80
.
So, you run this command:
docker run -d -p 8080:80 nginx
8080
is the host port (your computer).80
is the container port.Docker creates a connection between these two ports.
Now, when you open http://localhost:8080
in your browser, it connects to port 8080
on your computer, which forwards the request to port 80
in the container. Voilà! You see the NGINX welcome page.
RUN - Volume Mapping:
Volume mapping is a way to link a directory on your computer (host) to a directory inside a Docker container. This ensures that data persists even if the container is stopped or deleted, overcoming the temporary nature of container file systems.
You are working on a blog application and decide to use MySQL to store user data. You don’t want to install MySQL on your computer, so you run a MySQL Docker container:
docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -d mysql
Everything works fine. You connect the application to the MySQL database, create a table, and starts adding data.
The Problem: Data Loss
Later, you run:
docker stop mysql
docker rm mysql
When you restart the container:
docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -d mysql
You check for the data and discover it's all gone! Why? Because:
Containers have an independent file system.
When the container was removed, so was all the data stored inside it.
You realize you need a way to persist the data, so it stays even if the container is deleted.
The Solution: Volume Mapping
To prevent this problem, you use volume mapping. You link a directory on your computer (/opt/mydir
) to the directory where MySQL stores its data inside the container (/var/lib/mysql
):
docker run -v /opt/mydir:/var/lib/mysql mysql
Here’s what happens:
/opt/mydir
is the host directory where data will be stored./var/lib/mysql
is the container directory where MySQL writes its data.Docker ensures any changes in
/var/lib/mysql
are synchronized with/opt/mydir
.
Now, even if you stop and remove the container, the data remains in /opt/mydir
. When you start a new container, the new container finds the same data because it’s mapped to /opt/mydir
.
Inspect Container:
docker inspect <container_name/container_id>
Provides detailed information about containers, images, volumes, and networks in Docker. It returns information in JSON format about the specified object, such as configuration details, networking settings, environment variables, and more.
Container Logs
docker logs<container_name/container_id>
Container logs refer to the output generated by a containerized application running inside a Docker container. These logs provide information about the container’s activities, errors, and the status of processes inside the container. Container logs are crucial for debugging, monitoring, and understanding the behavior of applications in production.
How to write a Dockerfile?
We will understand it with the help of a tutorial. In this tutorial, we’ll walk through the steps to containerize a simple Go application using Docker. The Go application will be a basic web server that serves "Hello, World!" when accessed via a browser. We'll cover everything from writing the Go code to building and running it inside a Docker container.
Prerequisites
Before we start, make sure you have the following tools installed on your machine:
Go (Golang): We’ll be using Go 1.23.1 for this tutorial. You can download it from golang.org.
Docker: You’ll need Docker installed on your machine to create containers. You can download Docker Desktop from docker.com.
Step 1: Write a Simple Go Application
Let's start by creating a basic Go application that starts an HTTP server and displays a "Hello, World!" message.
1.1 Create the Project Directory
Open a terminal and create a directory for your Go application:
mkdir GoDockerized
cd GoDockerized
1.2 Write the Go Code (main.go
)
Inside the GoDockerized
directory, create a file named main.go
and add the following Go code:
// main.go
package main
import (
"fmt"
"net/http"
)
// handler function will respond with "Hello, World!"
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// Register the handler for the root URL "/"
http.HandleFunc("/", handler)
// Print a message indicating the server is starting
fmt.Println("Server starting on port 8080...")
// Start the server on port 8080
http.ListenAndServe(":8080", nil)
}
Step 2: Create a Dockerfile
Now that we have our Go application, we need to create a Dockerfile to containerize it. A Dockerfile is a set of instructions that Docker uses to build a container image.
2.1 Write the Dockerfile
In the same GoDockerized
directory, create a new file named Dockerfile
(without any extension). The contents of the Dockerfile are as follows:
# Step 1: Use the official Go image for Go 1.23 based on Alpine
FROM golang:1.23-alpine
# Step 2: Set the working directory inside the container
WORKDIR /app
# Step 3: Copy the Go source code into the container
COPY . .
# Step 4: Build the Go application
RUN go build -o main .
# Step 5: Expose the port that the app will run on
EXPOSE 8080
# Step 6: Define the default command to run the app
CMD ["./main"]
Explanation of the Dockerfile:
FROM golang:1.23-alpine
:We start with the official Go 1.23 image, based on Alpine Linux. Alpine is a lightweight Linux distribution that minimizes the size of our Docker image.
The Go 1.23 version ensures we are using the latest Go release for our application.
WORKDIR /app
:- This sets the working directory inside the Docker container to
/app
. All subsequent commands (likeCOPY
,RUN
,CMD
) will execute inside this directory.
- This sets the working directory inside the Docker container to
COPY . .
:- This copies the contents of the current directory on your host machine (your Go app) into the
/app
directory inside the container.
- This copies the contents of the current directory on your host machine (your Go app) into the
RUN go build -o main .
:- This command builds the Go application inside the container. The
-o main
flag tells Go to output the binary file asmain
, which will be the executable we run inside the container.
- This command builds the Go application inside the container. The
EXPOSE 8080
:- This tells Docker that the container will use port 8080 for communication. This is the port where our Go application will listen for HTTP requests.
CMD ["./main"]
:- This defines the default command that runs when the container starts. It tells Docker to execute the
main
binary that was built earlier, starting the Go application.
- This defines the default command that runs when the container starts. It tells Docker to execute the
Step 3: Build the Docker Image
Now that we have both the Go application (main.go
) and the Dockerfile, we can build the Docker image.
3.1 Build the Image
In the terminal, run the following command in the GoDockerized
directory to build the Docker image:
docker build -t godockerized .
Here’s a breakdown of the command:
docker build
: This command is used to build a Docker image from the Dockerfile.-t godockerized
: The-t
flag tags the image with a name (godockerized
), which will be useful for running or referencing this image later..
: The dot indicates the current directory, meaning Docker should use theDockerfile
and other files in this directory to build the image.
Once the image build process is complete, Docker will output the image layers and their respective build statuses. The build should take a few seconds to a minute, depending on your system.
Step 4: Run the Docker Container
Now that we’ve built the Docker image, we can run it in a container.
4.1 Run the Container
To run the Go application inside a container, use the following command:
docker run -p 8080:8080 godockerized
This command does a few things:
docker run
: Tells Docker to run a container from the specified image.-p 8080:8080
: Maps port 8080 on your local machine to port 8080 inside the Docker container. This allows you to access the Go web server through your local machine’s browser.godockerized
: This is the name of the image we created earlier.
After running the command, Docker will start a container based on the godockerized
image, and you should see output similar to this in your terminal:
Server starting on port 8080...
his confirms that the Go application is running and listening for HTTP requests on port 8080 inside the container.
4.2 Verify the Application is Running
Now, open a web browser and visit http://localhost:8080
. You should see the message:
Hello, World!
This confirms that your Go application is running successfully inside the Docker container, and you’re able to access it via the browser.
Pushing the image to Docker Hub:
To push the godockerized
image to Docker Hub, you'll need to follow a few steps. This involves logging into Docker Hub, tagging the image correctly, and then pushing it to your Docker Hub repository.
Step 1: Log in to Docker Hub from the Command Line
Before you can push your image to Docker Hub, you need to log in using your Docker Hub credentials.
Run this command in your terminal:
docker login
You will be prompted to enter your Docker Hub username and password. After successfully logging in, you’ll be able to push images to your Docker Hub account.
Step 2: Tag the Docker Image for Docker Hub
Docker images should be tagged with the Docker Hub username (or organization name) and the repository name.
For example, if your Docker Hub username is yourusername
, you need to tag the godockerized
image as follows:
docker tag godockerized yourusername/godockerized:latest
In this command:
godockerized
is the name of the local image you built.yourusername/godockerized
is the new tag with your Docker Hub username and the repository name you want to create (e.g.,godockerized
).latest
is the tag for the image version (you can use other tags likev1
,v2
, etc., if you prefer).
Step 3: Push the Image to Docker Hub
Now, you can push the tagged image to Docker Hub:
docker push yourusername/godockerized:latest
Pulling the Image from Docker Hub
To verify that the image was successfully pushed, you can try pulling it from Docker Hub to any machine that has Docker installed.
Run the following command:
docker pull yourusername/godockerized:latest
If you want to directly pull the image that I have created, run the following command:
docker pull nsahil992/godockerized:latest
How Applications are Containerized in Docker:
1. Docker CLI (Command Line Interface)
What it does: The user interacts with Docker via the CLI, using commands like
docker build
ordocker run
.Action:
- For example, when you run
docker run
, the CLI sends this command to the Docker Daemon.
- For example, when you run
2. Docker Daemon
What it does: The Daemon is the core engine of Docker. It listens to commands from the CLI or REST API and manages everything, including:
Building images.
Running containers.
Managing networks, storage, and other resources.
Communication:
The Daemon receives instructions from the CLI or REST API and executes them.
For example, if you ask to run a container, the Daemon will fetch the image, create the container, and allocate resources.
3. REST API
What it does: Allows external programs to interact with the Docker Daemon programmatically.
Action:
Tools like Docker Compose or Kubernetes use this API to talk to the Docker Daemon.
Instead of using the CLI, they send HTTP requests to the Daemon.
4. Container Creation Process
When you run docker run
(or an equivalent API call), the following steps occur:
Check for the Image:
The Daemon checks if the requested image exists locally.
If not, it downloads it from a registry like Docker Hub.
Set Up Isolation (Namespaces):
Docker uses namespaces to isolate the container’s processes, network, and file system from the host.
For example:
A new PID namespace ensures container processes don’t see or interfere with host processes.
A new network namespace gives the container its own virtual network.
Allocate Resources (Cgroups):
- The Daemon uses cgroups to manage how much CPU, memory, or other resources the container can use.
File System Setup (Union File System):
Docker sets up a layered file system:
The base layers are read-only (OS, libraries, etc.).
A writable layer is added on top for runtime changes.
Start the Container:
The Daemon starts the container and runs the specified command (e.g.,
python
app.py
).The container now operates independently, with its own isolated environment.
Resources:
Docker course [Kodekloud] : https://learn.kodekloud.com/user/courses/docker-training-course-for-the-absolute-beginner
GitHub small project : https://github.com/nsahil992/GoDockerized