Learn containers, Dockerfiles, and container deployment — Last updated: September 2026
Docker is a container platform that packages an application with everything it needs to run — the runtime, the dependencies, the configuration — into a single image that runs the same way on any machine with a container runtime. Docker deployment is the process of building that image, pushing it somewhere accessible, and running it as a container on a target environment.
Containers are the standard unit of deployment for a reason: they make the deployment environment explicit. The same image that you build and test locally is the image that runs in production. There is no “it works on my machine” because the machine is part of the image.
The deployment simulator at LearnToDeploy includes Docker-based projects where you write and debug Dockerfiles, build images, and figure out why a container fails to start or fails its health check.
A Dockerfile is a recipe for building a container image. Each instruction in the Dockerfile becomes a layer in the image. The final image is the result of running those instructions in order — a base image, plus your application code, plus your dependencies, plus the command that starts the application.
The Dockerfile is what makes a container build reproducible. Given the same Dockerfile and the same source code, you get the same image every time. That is what makes container deployment reliable: the thing you tested is the thing you deploy.
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 8000 CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
This Dockerfile does the following: starts from a Python base image, sets a working directory, copies the dependency file, installs dependencies, copies the application source, documents the port the application listens on, and declares the command that starts the application.
Building a Docker image runs the Dockerfile instructions and produces a layered image. Each instruction creates a new layer, and Docker caches layers so that rebuilds are fast when only later instructions change.
docker build -t myapp:1.2.0 . docker images docker tag myapp:1.2.0 myapp:latest
The build command takes a context — the directory whose contents Docker can access during the build. The
. at the end means the current directory. The build reads the Dockerfile in that directory and runs
its instructions.
Docker caches each layer. If an instruction has not changed, Docker reuses the cached layer instead of running it again. This is why the order of instructions in a Dockerfile matters for build speed.
A common optimization is to copy the dependency file first, install dependencies, and then copy the source code. If only the source code changes, Docker reuses the cached dependency layer and only re-runs the later instructions. If you copy all the source first, every change invalidates the cache from that point onward.
# Slower: copying everything before installing deps COPY . . RUN pip install -r requirements.txt # Faster: install deps first, then copy source COPY requirements.txt . RUN pip install -r requirements.txt COPY . .
A container is a running instance of an image. You start a container from an image, and the image's startup command runs inside the container. The container is isolated from the host — its own filesystem, its own network, its own process space.
docker run -d -p 8000:8000 --name myapp myapp:1.2.0 docker ps docker logs myapp docker stop myapp
-p 8000:8000 maps the host's port 8000 to the container's port
8000. Without this, the container is isolated and nothing can reach it from outside.-e PORT=8000 -e DATABASE_HOST=postgres injects
configuration into the container at runtime.-d runs the container in the background. Without it, the
container runs in the foreground and blocks the terminal.--name gives the container a predictable name for logs, stop, and
removal.
A container listens on a port inside the container. For anything outside the container to reach it, the port
must be mapped to the host. If the application listens on port 8000 inside the container and you map
-p 8000:8000, you can reach it at localhost:8000 on the host.
A common deployment failure is a port mismatch: the application listens on one port, but the platform — or the port mapping — expects another. The container is running, but nothing can reach the application, and the health check returns 502 Bad Gateway.
# The app listens on 8080, but the platform expects 8000 # This container starts successfully but is unreachable CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8080"] # The platform routes traffic to 8000, the container exposes 8000, # but the app is not listening there — connection refused curl http://localhost:8000/health # curl: (7) Failed to connect to localhost port 8000
Container failures fall into a few categories, and each has a different diagnostic path.
If a container exits as soon as it starts, the startup command is the first thing to check. The command might be wrong — a typo, a missing executable, a path that does not exist. The application might be crashing on startup because of a missing environment variable or a configuration error.
docker run --rm myapp:1.2.0 # If the container exits immediately, check the logs docker run --rm myapp:1.2.0 2>&1 | head -20 # Or run interactively to see the error docker run --rm -it myapp:1.2.0 /bin/bash
If the container is running but the health check fails or the port does not respond, check what the application
is actually listening on. The application might be listening on a different port than the one the platform
expects, or on 127.0.0.1 instead of 0.0.0.0.
# Inside the container, check what is listening ss -tlnp # Or check the application logs for the bind address docker logs myapp | grep -i bind
If the container is reachable but the health check fails, the application might be running but not healthy — the database is not connected, a dependency is unavailable, or the health endpoint itself is misconfigured. Check the application logs and the health endpoint directly.
curl http://localhost:8000/health # Compare the response against what the health check expects
python:3.12-slim is reproducible.
python:latest is not — it changes over time and can break your build without warning.
CMD or ENTRYPOINT, the
container exits immediately with no error — it has nothing to run.EXPOSE instruction
documents the port for humans and tooling. The application must actually listen on that port.myapp:1.2.0 is traceable. myapp:latest
is not — you do not know what code is running.The LearnToDeploy deployment simulator includes Docker-based projects where you practice writing and debugging Dockerfiles. The simulator responds with real Docker build output, container logs, and health check results — so you can practice reading Docker output and diagnosing container failures.
The simulator includes intentionally broken Dockerfiles — wrong base image tags, missing startup commands, port mismatches, missing dependencies — so you can practice diagnosing the most common container deployment problems in a safe environment.
For the underlying concepts, see the deployment concepts library, including entries on Docker and containers, ports and networking, and health checks.