Images, containers, and Dockerfiles: everything a beginner needs to know.
Content Outline
- Introduction to Docker and Containers
- Important Docker Terms
- Basic Docker Commands — Running and managing Docker images and containers
- What is a “Dockerfile” ?
- Building and Running Custom Docker Images
- Conclusion
Introduction to Docker and Containers
Imagine this: You’re a developer who created an app on your computer. It works perfectly! But when your teammate tries to run it, they get errors because their computer is different from yours. Frustrating, right?
This is where Docker comes in. Docker lets you package your app in a “container” — think of it like a shipping container that holds everything your app needs to run:
- The app itself
- All required software
- Configuration files
- System settings
Why is this useful?
- Works everywhere: Your container runs the same way on any computer
- No conflicts: Each container is isolated, so apps don’t interfere with each other
- Easy sharing: Send your container to anyone, and it’ll work instantly
- Resource friendly: Lighter than virtual machines
Real-world example ??? Think of a container like a food delivery box. It has:
- The main dish (your app)
- All needed ingredients (dependencies)
- Cooking instructions (configuration)
- Its own space (isolation)
Just like how that box ensures your food arrives exactly as the chef prepared it, Docker ensures your app runs exactly as you built it.
Important Docker Terms
Here are some key terms / components / concepts you’ll frequently come across when working with Docker:
- Docker Image: A read-only template containing the operating system, code, and dependencies.
- Docker Container (or simply Container): A running instance of a Docker image.
- Image Registry: A storage and distribution system for Docker images.
- Dockerfile: A script that defines how a Docker image is built.
- Layer: A modification or addition to a Docker image, forming part of its structure.
Let’s look at each key terms / concepts (using simple examples):
i. Docker Image
Think of it like a recipe:
- Contains exact instructions to create your app’s environment
- Includes code, runtime, libraries, dependencies
- Can’t be changed once created (read-only)
- Examples: Ubuntu image, Node.js image, or your custom app image
ii. Docker Container
Think of it like a running restaurant:
- It’s your image in action
- Multiple containers can run from one image
- Each container is isolated from others
- Has its own resources (CPU, memory, network)
iii. Docker Registry
Think of it like an app store:
- Where Docker images are stored and shared
- Docker Hub is the most popular registry
- Companies often have private registries
- You can push/pull images from registries
iv. Dockerfile
Think of it like a set of building instructions:
- Text file that creates Docker images
- Lists steps to set up your environment
- Each instruction creates a layer
- Named exactly as ‘Dockerfile’
v. Docker Daemon
Think of it as the kitchen manager:
- Runs in background on your computer
- Builds and runs containers
- Manages Docker objects
- Handles container lifecycle
vi. Docker Client
Think of it as the waiter taking orders:
- Command-line tool (docker)
- Communicates with Docker daemon
- Client sends commands to Docker daemon
- Daemon executes and sends back results
vii. Layers
Think of them like steps in a recipe:
- Each instruction in Dockerfile creates a layer
- Layers are cached for faster builds
- Shared between images to save space
✅ Remember :
These components work together like a well-oiled machine. Images are your blueprints, containers are running instances, registry stores them, Dockerfile creates them, daemon manages them, and client controls them.
Basic Docker Commands — Running and managing Docker images and containers
Docker provides thousands, if not millions, of pre-built images (like ready-made recipes) that you can use for your specific use case. You can either use these images as they are (like following a recipe exactly) or use them as a starting point to create your own custom image (similar to tweaking a recipe to suit your taste).
For example, if you need to test your code on Ubuntu, you can simply pull a pre-built Ubuntu image and run it instead of setting up an entire virtual machine with the Ubuntu OS.
In this section, we’ll cover the following:
- Pulling and removing Docker images
- Running containers from docker image with different configurations
- Managing running containers
Before proceeding, let’s ensure Docker is installed on your local machine. To verify, run the docker
command in your terminal. If Docker is properly installed, you should see an output similar to this:
If you don’t see this, it means Docker isn’t installed. Follow the instructions in this guide to set it up: Docker Installation Guide.
Now that Docker is set up on your machine, let’s get started with running a pre-built Docker image and interacting with containers!!
Think of Docker like a restaurant franchise system.
Let’s understand how to run and manage Docker images:
Pulling and Managing Docker Images
Pull an image from Docker Hub :
docker pull python:3.9
Pull specific version :
docker pull python:3.9-slim
Output will look something like this:
See all the local ( including the downloaded pre-build ones) images :
docker images
Output will look something like this:
You can also removes a specific Docker image:
docker rmi python:3.9-slim
Remove all unused images (free space):
docker image prune
Running Containers from Image
To run a Docker container, use the docker run
command followed by the image tag, like this: docker run image_tag/image_name
.
You can also configure the container using various flags. Here are some commonly used flags you can take advantage of:
-it
: Get an interactive terminal (when you need to type commands)-d
: Run in background (for services/servers)-v
: Share files between your computer and container--name
: Give container a friendly name--memory
: Set maximum RAM usage--cpus
: Limit CPU usage--gpus
: Enable GPU access
I will explain these flags using different commands.
Basic run command :
docker run python:3.9-slim
Run container in Interactive terminal :
docker run -it python:3.9 bash
Output will look something like this:
Run container in background (detached) :
docker run -d python:3.9
Run with name :
docker run --name my-python python:3.9
Run container with defined resource limits :
docker run -memory=512m -cpus=2 python:3.9
Run with port mapping :
docker run -p 5000:5000 python:3.9
Set environment variables when running container :
docker run -e DB_HOST=localhost python:3.9
Mount volume (share files) to container :
docker run -v /local/path:/container/path python:3.9
Allow gpu access to container :
docker run --gpus all python:3.9
Managing Running / Stopped Containers
There are various commands available to manage containers, whether they are running or stopped. Let’s go through them one by one.
See what’s running :
docker ps
Output will look something like this:
See all containers (including stopped) :
docker ps -a
Output will look something like this:
Stop a container :
docker stop container_id
Start an existing stopped container :
docker start container_id
Restarts a running or stopped container :
docker restart container_id
Remove a stopped container :
docker rm container_id
Monitor running container resource usage :
docker stats container_name_or_id
Output will look something like this:
Some Pro Tips to Keep in Mind :
- Always name your containers with
--name
for easier reference - Use
docker ps -a
to find containers taking up space - Clean up regularly with
docker system prune
- Start with interactive mode (
-it
) when learning/debugging - Use background mode (
-d
) for running services
What is a “Dockerfile” ?
A Dockerfile is like a recipe that tells Docker how to build an image. It’s basically a text file which contains a series of instructions that define what should be included in the image and how it should behave. Each instruction in the Dockerfile creates a new layer in the resulting Docker image.
Let’s look at 👇🏻
Key Roles of a Dockerfile :
i. Base Image Specification (FROM):
- Defines the starting point for the image, such as a specific operating system or a pre-built environment (e.g.,
ubuntu
,node
,python
). - Example:
FROM ubuntu:20.04
ii. Application Code Integration (COPY):
- Copies your application code and files into the image.
- Example:
COPY . /app
iii. Environment Setup (RUN):
- Allows you to install software, copy files, and configure settings required by the application.
- Example: Installing dependencies with
RUN apt-get install -y curl
orRUN pip install -r requirements.txt
iv. Port Declaration (EXPOSE) :
- Specifies the network ports that the container will listen on at runtime.
- Example:
EXPOSE 5000
declares that the container listens on port5000
. - Note: To make this port accessible from outside the container, you must explicitly map it when running the container using the
-p
flag.
v. Command Specification (CMD):
- Defines the commands to run when the container starts, such as starting a server or an application.
- Example:
CMD ["python", "app.py"]
Example Dockerfile
A Dockerfile looks something like this :
# Use an official Python runtime as the base image
FROM python:3.9-slim
# Set environment variables
ENV APP_ENV=production
ENV APP_PORT=5000
# Set the working directory inside the container
WORKDIR /app
# Copy application code into the container
COPY . /app
# Install dependencies
RUN pip install -r requirements.txt
# Declare the port on which the app will run
EXPOSE 5000
# Define the command to run the application
CMD ["python", "app.py"]
Generally, a Dockerfile begins by specifying the base image, followed by copying the application code into the image. Next, it includes all the necessary RUN
commands to be executed during the image build process, such as installing dependencies. Finally, the entry point for the Docker image is defined to specify how the container should start.
Few things in regards of Dockerfile:
- Must be named exactly “Dockerfile”
- Each instruction creates a layer
- Instructions run in order, top to bottom
Now that we have a clear understanding of Dockerfile, let’s proceed to create our own custom image using it.
Building and Running Your Own Docker Image
To better understand how to build custom images, we’ll create a Flask API, build an image for it, and run the Flask API inside a container.
We’ll go through it step by step.
You can find the project code here : https://github.com/rumanxyz/docker-flask-app
1. Project Structure
First, let’s set up our project:
./docker-flask-app/
├── Dockerfile
├── app.py
└── requirements.txt
2. Application Files
requirements.txt
Here, we will specify all the necessary Python packages required to run the API.
flask==2.3.3
app.py
Flask API which will be running on port 5000
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/api/hello', methods=['GET'])
def hello():
return jsonify({"message": "Hello from Docker!"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Dockerfile
# define base image
FROM python:3.10-slim
# set working directory
WORKDIR /app
# copy requirements first (for better caching)
COPY requirements.txt .
# install dependencies
RUN pip install -r requirements.txt
# copy application code
COPY . .
# expose port to acess conrainer
EXPOSE 5000
# run command
CMD ["python", "app.py"]
Now that we’ve written our application files, let’s test them locally first. When you run the Flask app, you’ll see an output similar to this:
If we open http://127.0.0.1:5000/api/hello
locally, we’ll see an API response similar to this:
Now that we’ve confirmed our API is working, let’s proceed to build a Docker image for it.
3. Building the Docker Image
Let’s build the image with no cache (clean build)
docker build --no-cache -t my-flask-api:1.0 .
Please Note :
-t
tags your image (name:version).
means use current directory- Always version your images (1.0, 1.1, etc.)
On running the build command you’ll see something like this:
Now, Let’s check the docker images and see if image was build or not. We can do this by running docker images
. And here you can see my-flask-api
image with version 1.0
created recently.
Now, let’s check the Docker images to see if the image was built successfully. We can do this by running docker images
. You should see the my-flask-api
image with version 1.0, created recently.
4. Running Your Container
Run container with environment variables and custom name
# Run with environment variables
docker run -d -p 5000:5000 \
--name my-api-test \
-e FLASK_ENV=development \
my-flask-api:1.0
Here “my-flask-api:1.0” is the docker image which we created.
If we run docker ps
, then you’ll see the container running.
Now let’s go and test the API.
5. Testing Your API
We can test this with the curl command :
curl http://localhost:5000/api/hello
The output I received after running this is:
We successfully created a Flask app, dockerized it using a Dockerfile, ran the container with the image, and finally tested the API.
But some common issues you may see and [possible] solution :
Port already in use:
- Stop other containers using port 5000
- Or use different port:
-p 5001:5000
Container exits immediately:
- Check logs:
docker logs my-api
- Make sure CMD is correct
- Try running in interactive mode
Can’t access API:
- Verify container is running
- Check port mapping
- Ensure host=’0.0.0.0' in Flask app
Conclusion
Docker has become an important tool in modern software development, simplifying how we build, ship, and run applications. Through this article, we’ve covered the fundamentals — from understanding containers and images to running pre-built images and creating your own custom containers. By mastering basic Docker commands and understanding Dockerfile syntax, you’re now equipped to containerize your applications.
Remember that Docker’s real power lies in its consistency and portability —
“it works on my machine” becomes “it works everywhere.”
As you continue your Docker journey, explore Docker Compose for managing multiple containers, dive into container networking, and learn about container orchestration with tools like Kubernetes. The official Docker documentation (docs.docker.com) is an excellent resource for deeper exploration of these topics.
If you enjoyed this article, your applause would be greatly appreciated!