Home › Guides › Docker Tutorial for Beginners
Tutorial · 2026Docker Tutorial for Beginners 2026: Containerise a Python API in 7 Steps
This Docker tutorial for beginners does not stop at docker run hello-world. It takes you from an empty folder to a running, hardened, multi-stage container image serving a real HTTP endpoint. Docker Engine 29.7.2 was the stable release as of 6 August 2026, and every command below was checked against it.
- An image is a filesystem plus a start command. A container is one running copy of it. Almost every beginner bug comes from confusing the two.
-
Layer order decides your build speed. Copy
requirements.txtand install dependencies before you copy your source code, or every one line edit reinstalls everything. - Bind to 0.0.0.0, never 127.0.0.1. A server bound to localhost inside a container is unreachable from your laptop, and this single line breaks more first builds than anything else.
- Docker Hub rate limits are real. Docker's official usage documentation puts unauthenticated pulls at 10 per hour and free authenticated personal accounts at 100 per hour.
- Multi-stage builds plus a non root user turn a demo image into something a reviewer will accept in production.
- Compose is not a different tool. It is the same engine driven by one YAML file, and it is how you add a database in about fifteen lines.
If you have ever been handed a repository, run pip install -r requirements.txt, and watched it fail on your machine for reasons nobody can explain, you already understand why Docker exists. This Docker tutorial for beginners is built around one small but complete piece of work: a Python pricing API that you will write, containerise, run, debug, harden and then wire into a database. By the end you will have a Dockerfile you can reuse on Monday, not a mental model you will forget by Friday.
What You Will Build in This Docker Tutorial for Beginners
The application is deliberately tiny so the container work stays visible. It is a FastAPI service with two endpoints: a health check, and a pricing endpoint that applies 18 percent GST to a quantity. FastAPI 0.141.1 was published to PyPI on 29 July 2026 and requires Python 3.10 or newer, so the Python 3.13 base image used here is well inside the supported range.
Prerequisites and a 30 second sanity check
You need Docker Desktop or Docker Engine installed, and nothing else. No Python on your host, no virtual environment, no compiler. That is the point. Confirm the daemon is actually running before you write a single line:
docker version --format '{{.Server.Version}}'
docker run --rm hello-world
If the first command prints a version and the second prints a greeting, you are ready. If the first errors with Cannot connect to the Docker daemon, Docker is installed but not started, which is a different problem from Docker being missing.
How Docker Actually Works: Images, Layers and Containers
Before the commands, get the four nouns straight. Almost every confusing error message in your first week is really a category error between these.
| Term | What it actually is | How you spot it |
|---|---|---|
| Image | A read only stack of filesystem layers plus metadata saying which command to run |
docker images, has a tag like priceapi:0.1
|
| Layer | The filesystem diff produced by one instruction in your Dockerfile, cached and reused by hash | docker history priceapi:0.1 |
| Container | One running or stopped instance of an image, with its own writable top layer |
docker ps -a, has a random name like brave_hopper
|
| Volume | Storage that lives outside the container's writable layer and survives deletion | docker volume ls |
The practical consequence: deleting a container never deletes your image, and rebuilding an image never touches your volumes. When a beginner says "I lost my database", they usually deleted a container that had no volume attached, so the writable layer went with it.
Why layer caching decides your build speed
Docker builds top to bottom and caches each instruction. If instruction five changes, five onward rebuild and everything above is reused. Your dependency install is slow and your source code changes constantly, so the install must sit above the code copy. Get this backwards and every typo fix triggers a full pip install. This is the single highest leverage habit in the whole tutorial.
Steps 1 to 3: Write the API, the Requirements and Your First Dockerfile
Step 1. Create a folder and add main.py. The pricing logic is real arithmetic so you have something verifiable to curl later:
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
@app.get("/price/{qty}")
def price(qty: int):
unit = 249.0
gst = 0.18
total = round(qty * unit * (1 + gst), 2)
return {"qty": qty, "unit": unit, "total_with_gst": total}
Step 2. Add requirements.txt. Pin the version. An unpinned dependency means your image is a different image every time you build it, which defeats the purpose of containers:
fastapi[standard]==0.141.1
Step 3. Add a file called Dockerfile with no extension. Read the comments, because the ordering is the lesson:
FROM python:3.13-slim-bookworm
WORKDIR /app
# Dependencies first: this layer is cached until requirements.txt changes
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Source code last: edits here rebuild only this layer and below
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
This Dockerfile example for Python, line by line
Three details worth understanding rather than copying. The slim-bookworm tag is a Debian based image without build toolchains, and the 3.13 tag currently resolves to Python 3.13.15 on Docker Hub. --no-cache-dir stops pip from leaving a wheel cache inside the layer, which is dead weight in a shipped image. And --host 0.0.0.0 tells the server to accept connections on every interface in the container, not just its own loopback.
The .dockerignore file nobody tells beginners about
Without this, COPY . . ships your .git history, your virtual environment and possibly your .env secrets into the image. Create .dockerignore:
.git
.venv/
__pycache__/
*.pyc
.env
Steps 4 and 5: Build, Run and Debug the Container
Step 4. Build the image and tag it. The trailing dot is the build context, meaning the directory Docker sends to the daemon:
docker build -t priceapi:0.1 .
docker images priceapi
Step 5. Run it, mapping host port 8000 to container port 8000. The --rm flag deletes the container when it stops so you do not accumulate dead containers:
docker run --rm -p 8000:8000 priceapi:0.1
In a second terminal, verify the worked example end to end. Ten units at 249 rupees is 2,490, and 18 percent GST takes it to 2,938.20:
curl http://localhost:8000/price/10
# {"qty":10,"unit":249.0,"total_with_gst":2938.2}
If you get that JSON back, you have done the actual thing: source code on your disk, dependencies you never installed locally, and a process running in an isolated namespace answering HTTP.
Common first build errors and what they actually mean
| Symptom | Real cause | Fix |
|---|---|---|
| curl returns "Empty reply" or connection reset | Server bound to 127.0.0.1 inside the container | Add --host 0.0.0.0 to the CMD |
Bind for 0.0.0.0:8000 failed: port is already allocated |
Another container or process holds the host port |
docker ps, stop it, or map -p 8001:8000
|
toomanyrequests: You have reached your pull rate limit |
Anonymous pulls capped at 10 per hour by Docker Hub |
docker login to move to the 100 per hour free tier |
| Container exits immediately, no logs | CMD process finished or crashed on startup |
docker logs <container> then docker run -it --entrypoint sh image
|
| Code edits do not appear in the container | You rebuilt nothing; the image is a snapshot | Rebuild, or bind mount the source for local dev |
That last row deserves a habit rather than a fix. During development, mount your code instead of rebuilding on every save:
docker run --rm -p 8000:8000 -v "$(pwd)":/app priceapi:0.1 \
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
Container fundamentals like these are the entry point to the wider DevOps toolchain, and they are exactly where 360DT's live AWS Solutions Architect and DevOps Engineer course starts before moving into ECS, pipelines and infrastructure as code.
Step 6: How to Dockerize a Python Application Properly with Multi-Stage Builds
The Dockerfile above works, but it is not what you would put in front of a security review. Learning how to dockerize a Python application properly comes down to two more changes: build artefacts should not travel into the final image, and the process should not run as root. A multi-stage build gives you both.
FROM python:3.13-slim-bookworm AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.13-slim-bookworm
RUN useradd --create-home --uid 10001 appuser
WORKDIR /app
COPY --from=builder /install /usr/local
COPY --chown=appuser:appuser . .
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
The first stage installs into /install. The second stage copies only that directory across, so pip's own machinery, any transient build files and the first stage's cache never reach the shipped image. USER appuser means a container escape lands an attacker on an unprivileged account with UID 10001 rather than root. Verify it took effect:
docker build -t priceapi:0.2 .
docker run --rm priceapi:0.2 id
# uid=10001(appuser) gid=10001(appuser) groups=10001(appuser)
If that prints uid=0(root), your USER line is below the CMD or missing entirely.
-
Baking secrets into the image. An
ENV API_KEY=...line is visible forever indocker history, even if a later layer deletes it. Pass secrets at run time with-eor a secrets manager. -
Using the
latesttag. It is not a version, it is a default label, and it silently changes under you. Pin real versions. -
Running
apt-get updatein a separate RUN fromapt-get install. The cached update layer goes stale and you install old packages. - Storing data in the container filesystem. Anything not on a volume dies with the container.
-
Ignoring image size. A full
python:3.13base is several times larger than the slim variant, and you pay for that on every pull in every pipeline.
Docker Compose Tutorial: Add a Postgres Database in One File
Real services need dependencies. The first thing to understand in any Docker Compose tutorial is that Compose is not a separate technology; it is a declarative front end to the same engine you have already been driving, and it creates a private network where containers reach each other by service name. Create compose.yaml:
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://app:secret@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-bookworm
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 10
volumes:
pgdata:
Then bring the whole stack up and tear it down:
docker compose up --build
docker compose logs -f api
docker compose down # keeps the pgdata volume
docker compose down -v # deletes it too
Note the hostname db inside DATABASE_URL. That is the service name, resolved by Docker's internal DNS. Beginners routinely write localhost there, which inside the API container means the API container itself. The condition: service_healthy line is the fix for the other classic race, where the API starts before Postgres is accepting connections and crashes on its first query.
This same pattern underpins how data platforms and ML services are shipped. If your interest is the data side rather than the application side, containerised pipelines show up throughout Microsoft Fabric data engineering work and in MLOps engineering, where the model server is just another image on a registry.
Go from your first Dockerfile to production cloud infrastructure
A 12 week live weekend program that prepares you for both AWS Solutions Architect Associate (SAA-C03) and AWS DevOps Engineer Professional (DOP-C02). Includes hands on projects, mentor support and placement guidance.
Explore the course
Docker Commands for Beginners: The Reference You Will Actually Use
The docker commands for beginners that actually matter number about fifteen, and they cover roughly ninety percent of daily work. Keep this table open for your first month.
| Command | What it does | When you reach for it |
|---|---|---|
docker build -t name:tag . |
Builds an image from the Dockerfile in the current directory | Every code change you want to ship |
docker run --rm -p 8000:8000 name:tag |
Starts a container and removes it on exit | Testing an image locally |
docker ps -a |
Lists containers including stopped ones | Finding the container that died on you |
docker logs -f <container> |
Streams stdout and stderr | First move on any crash |
docker exec -it <container> sh |
Opens a shell inside a running container | Checking files, env vars, network from the inside |
docker run -it --entrypoint sh name:tag |
Shells into an image that will not stay running | Debugging a container that exits instantly |
docker history name:tag |
Shows every layer and its size | Hunting for image bloat or leaked secrets |
docker system df |
Reports disk used by images, containers, volumes, cache | When your laptop runs out of space |
docker system prune -a |
Deletes unused images, networks and build cache | Reclaiming that space, carefully |
docker compose up --build |
Builds and starts every service in compose.yaml | Running a multi container stack |
Also read: Retrieval Augmented Generation Explained in 2026: How RAG Actually Works, Step by Step, which walks through a system you would deploy exactly this way.
Step 7: Push the Image and Automate the Build
An image that only exists on your laptop is not much use. Step 7 is publishing it to a registry and letting CI rebuild it on every push. Tag with your Docker Hub username, log in, and push:
docker login
docker tag priceapi:0.2 yourusername/priceapi:0.2
docker push yourusername/priceapi:0.2
Then let GitHub Actions do it automatically. GitHub's Free plan includes 2,000 Linux minutes per month for private repositories plus 500 MB of artifact storage, and workflows on public repositories using standard hosted runners are free on every plan, so this costs nothing to practise. Save this as .github/workflows/build.yml:
name: build
on: [push]
jobs:
image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- run: docker build -t ${{ secrets.DOCKERHUB_USER }}/priceapi:${{ github.sha }} .
- run: docker push ${{ secrets.DOCKERHUB_USER }}/priceapi:${{ github.sha }}
Tagging by github.sha rather than latest means every image traces back to exactly one commit, which is what makes a rollback a one line change instead of an investigation.
Where this leads next
From here the path forks by role, and all of them assume the container skills you just practised. Orchestration with Kubernetes or ECS is the next step for platform work, and the same primitives appear in Azure DevOps and AZ-400 pipeline work. If you are heading towards applied AI, model servers and retrieval services are shipped as containers too, which is why containerisation sits underneath agentic AI and RAG engineering rather than beside it. Analysts are not exempt either: refresh jobs behind a Power BI and PL-300 workflow increasingly run as scheduled containers. The full certifications overview maps which credential fits which of those tracks.
Also read: AZ-305 vs SAA-C03 in 2026: Which Cloud Architect Certification Is Better for Indian Careers? and DevOps Engineer Salary in India 2026.
Docker Tutorial for Beginners: Your Production Readiness Checklist
Before you call any image finished, run down this list. Every item maps to something in the tutorial above.
-
Pinned base image with a specific tag, never
latest. - Dependencies installed above the source copy so the cache works.
-
A .dockerignore covering
.git,.venvand.env. - Multi-stage build so build tooling stays out of the shipped image.
-
A non root USER, verified with
docker run --rm image id. -
No secrets in any layer, confirmed with
docker history. - Persistent data on a named volume, not the container filesystem.
- An immutable tag per commit in CI, so rollbacks are trivial.
Frequently asked questions
Is this Docker tutorial for beginners enough to start using Docker at work?
For containerising and running a service, yes. If you can write the multi-stage Dockerfile above from memory, debug a container that exits immediately, and explain why dependencies are copied before source code, you can containerise most small services. What this tutorial does not cover is orchestration, meaning how dozens of containers get scheduled, scaled and healed across a cluster. That is Kubernetes or ECS, and it is the natural next subject.
What is the difference between a Docker image and a container?
An image is a read only template: a stack of filesystem layers plus metadata saying what command to run. A container is one running instance of that image with a thin writable layer on top. One image can produce hundreds of containers. Deleting a container leaves the image untouched, and anything written inside the container's writable layer disappears with it unless you mounted a volume.
How do I dockerize a Python application that needs system packages?
Add an apt install step, and keep the update and install in the same RUN instruction so the package index is never stale in a cached layer. For example: RUN apt-get update && apt-get install -y --no-install-recommends libpq-dev && rm -rf /var/lib/apt/lists/*. In a multi-stage build, put heavy build dependencies in the builder stage only, so they never reach the final image.
Do I need Docker Compose if I only have one container?
No, but it is still worth it. A compose.yaml records your ports, environment variables and volumes in a file that goes into version control, instead of living in a long docker run command in your shell history. The moment you add a database, cache or message queue, Compose stops being optional.
Why does my container work locally but fail in CI?
Three causes cover most cases. First, architecture: an image built on an Apple Silicon Mac is arm64 and will not run on an amd64 runner unless you build for the right platform. Second, cache: your local build reused layers that CI has to fetch fresh, which exposes an unpinned dependency that has since changed. Third, Docker Hub rate limits, since an unauthenticated runner gets only 10 pulls per hour. Adding a registry login step to the workflow fixes the third.
How much does it cost to practise Docker and CI as a student in India?
Nothing, if you stay inside the free tiers. Docker Engine and Docker Desktop for personal use, Docker Hub public repositories, and GitHub Actions on public repositories all cost zero. GitHub's Free plan also includes 2,000 Linux minutes per month for private repositories. The realistic constraint is disk space on your laptop, which is why docker system prune becomes a weekly habit.
Should I learn Docker before Kubernetes?
Yes, and the gap is bigger than most roadmaps admit. Kubernetes schedules and heals containers; it assumes you already know what an image is, why a process must bind to 0.0.0.0, how environment variables reach a process and where data persists. Debugging a Kubernetes pod is mostly debugging a container with more layers of indirection, so time spent on Docker fundamentals pays back directly.
Is Docker still relevant in 2026 given serverless and managed platforms?
The container image is now the common packaging format across almost every deployment target, including many serverless products that accept an image rather than a zip. You may write fewer Dockerfiles by hand as buildpacks and platform tooling improve, but reading one, debugging one and reasoning about layers and users remains basic literacy for anyone shipping software.
The honest next step after a tutorial is a project with real constraints: put this image on a cloud runtime, give it a managed database, add a pipeline, then break it and fix it. If you would rather do that with an instructor watching your terminal, the live AWS Solutions Architect and DevOps Engineer course runs Saturday and Sunday, 8:00 to 11:00 PM IST across 12 weeks, or you can start with a free webinar and a demo class before committing to anything.
About this guide. 360 Digital Transformation is an independent training provider. We are not affiliated with the certification bodies, vendors or open source projects mentioned, and our courses are exam preparation rather than official training. Tools and versions change quickly; commands and figures cited were checked on 9 September 2026.
