Docker is easy to demonstrate and surprisingly difficult to explain well.
You can copy a docker run command, open a port and see a web page within a minute. That feels like progress. But then the obvious questions arrive: where did the files come from? Why does 8080:80 contain two numbers? What survives when the container is removed? When should a command become a Compose file? And is this configuration safe enough to expose to the internet?
Those questions are not side notes. They are Docker.
That is why OnlinePilots is starting with one complete interactive learning path: Build your first Docker web server. Instead of presenting isolated commands, the module lets you construct, inspect, break, repair and finally deploy one small system. Every step changes a live model of the same container.
The goal is not to memorize Docker syntax. It is to learn how to reason about containers.
Start the interactive Docker module →
Why a web server is the right first project
A basic web server is small enough to understand but complete enough to reveal Docker's important boundaries.
It has an image, a running process, a container filesystem and an internal network port. It needs a route from your computer to the container. It becomes much more useful when website files live outside the disposable container layer. It can be described in Compose and, once exposed beyond localhost, it needs sensible security controls.
Nothing is artificial. The model you learn is the same one you will later use for a personal website, internal dashboard, home-lab service or small production application.
The mental model: image, container and host
Three objects do most of the conceptual work.
An image is the packaged starting point. Docker describes an image as a standardized package containing the files, binaries, libraries and configuration required to run a container. Images are immutable and built in layers. Selecting nginx:1.28-alpine therefore means selecting both an application and a particular packaged environment.
A container is a runnable instance of that image: an isolated process with its own filesystem, networking and process tree. It is not a small virtual machine. Containers share the host kernel, which is one reason they can be lightweight.
The host is the machine running Docker. It owns the published port, stores persistent data and enforces the resources and permissions available to the container.
The interactive module keeps these objects visible at the same time. When you change a port or attach storage, the infrastructure view changes with you. That connection between a configuration choice and its effect is where the learning happens.
The eleven-step journey
1. Select an image
The path starts with nginx because it provides a real web server without requiring application code. You compare movable tags such as nginx:alpine with a version-bound tag such as nginx:1.28-alpine.
A movable tag is convenient for experiments. A version-bound reference makes deployments more reproducible because an update is an explicit decision. For strict reproducibility, production teams can go one step further and pin an image digest.
2. Name the container
Docker can generate a name, but a deliberate name makes commands, logs and monitoring easier to understand. my-webserver communicates intent; a random adjective-and-scientist combination does not.
This also introduces a useful distinction: the image is the template, while the named container is one runtime instance created from it.
3. Map a port
The notation 8080:80 becomes easier when you read it from outside to inside:
browser → localhost:8080 → container:80 → nginx
Port 80 belongs to nginx inside the container. Port 8080 belongs to your Docker host. Publishing the port creates a route between them. Docker warns that published ports are accessible beyond the host by default, so binding and firewall choices matter when moving from a laptop to a server.
4. Start the container in a simulator
The first complete command now has meaning:
docker run -d --name my-webserver -p 8080:80 nginx:1.28-alpine
The simulator applies the command without touching a real Docker daemon. You see the container enter a running state and the network route become active. This is a safe place to make mistakes: an invalid name or port produces feedback before anything is deployed.
5. Inspect the virtual docker ps
Starting is not the same as verifying. The module renders a realistic docker ps view so you can check the image, status, port mapping and name.
That verification habit scales. On a real host, you should also inspect container health, logs and the HTTP response. A deployment is complete only when the service behaves as intended.
6. Remove the container
Next, you remove the runtime instance. The image remains available, but the container and its writable layer are gone. This deliberately breaks the feeling that a container is a permanent little server.
Containers should be replaceable. Configuration and important data need a life outside one container instance.
7. Discover what disappeared
Files written only inside a container's writable layer disappear when that container is destroyed. This is not a Docker failure; it is the expected lifecycle.
The lesson makes the loss visible before introducing the solution. Understanding the problem first turns volumes from mysterious syntax into an obvious architectural requirement.
8. Add a volume
The website directory is mounted into nginx:
-v ./site:/usr/share/nginx/html:ro
The local ./site directory belongs to the host. nginx sees it at /usr/share/nginx/html. The :ro suffix makes the mount read-only inside the container, which is appropriate for static website files.
Docker's documentation explains that a volume's contents live outside the lifecycle of a given container. Bind mounts and Docker-managed volumes solve related but different problems: a bind mount is useful when you want direct access to host files, while a named volume is a strong default for application-generated persistent data.
9. Convert the configuration to Compose
A long shell command works, but it is easy to forget and difficult to review. Compose moves the desired state into a YAML file:
services:
web:
image: nginx:1.28-alpine
container_name: my-webserver
ports:
- "8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
restart: unless-stopped
The interactive editor explains the selected line immediately. Ports, storage and restart policy stop being flags hidden in terminal history and become a readable deployment model that can be reviewed, versioned and repeated.
10. Run a security and configuration check
Containers provide isolation, but isolation is not the same as automatic security. The module checks a practical baseline:
- use a version-bound image;
- mount published website files read-only;
- make the container filesystem read-only where possible;
- prevent privilege escalation;
- drop unnecessary Linux capabilities;
- set CPU and memory limits.
The result is guidance rather than a certification. Real security also depends on image provenance, timely updates, secret management, the Docker host, network exposure, logging and backups. Docker also documents rootless mode, which lets the daemon and containers run without root privileges as an additional host-level measure.
11. Deploy the same model on your own host
Only now do you leave the simulator. The final step translates the model into a short operational routine:
docker compose config
docker compose up -d
docker compose ps
docker compose logs --tail=100 web
curl -I http://localhost:8080
First validate the Compose model. Then start it, inspect its state, read its logs and test the actual HTTP endpoint. If a reverse proxy or Cloudflare Tunnel sits in front of it, verify that layer separately and avoid exposing the origin port more broadly than necessary.
The four interactive features behind the module
The learning path combines four tools because each answers a different question.
1. Docker command simulator
What will this command do? The simulator turns options into observable state without requiring a local environment. It gives immediate feedback while preserving the real command shape.
2. Compose editor with direct explanation
What does this line control? Selecting a line connects YAML structure to runtime behavior. This is especially useful because indentation and nesting carry meaning in Compose.
3. Visual network and volume view
Where does traffic or data go? The diagram separates browser, host, container and storage. It updates as the learner publishes a port, starts a container or attaches a volume.
4. Automatic security and configuration check
What am I overlooking? The check turns abstract advice into specific, inspectable configuration. It also makes clear that a working deployment and a responsibly configured deployment are different milestones.
What you should understand at the end
After completing the module, you should be able to explain—not merely repeat—the following:
- an image is a packaged, immutable starting point;
- a container is a replaceable runtime instance;
- a port mapping connects a host port to a container port;
- data in the container layer is disposable;
- volumes or bind mounts keep important data outside that lifecycle;
- Compose records the desired multi-container application model;
- verification includes state, logs and an application-level response;
- security comes from deliberate layers, not from using Docker alone.
That foundation matters more than memorizing a dozen commands. Once the model is clear, new services become variations of the same questions: what runs, what is exposed, what persists, what is trusted and how do you prove it works?
Start in the simulator, finish on your own host
OnlinePilots is building toward a place where people learn to think in containers. A real cloud terminal and temporary Docker environments may become useful later. The first version deliberately starts smaller: a command simulator, an explainable Compose editor, a visual infrastructure model and a focused security check.
That is enough to cross the most important gap—from copying a command to understanding a deployment.
Build your first Docker web server →