LFCA 122 ๐ง Real-World Scenario โ Deploying a Container
Containers are not virtual machines. A container is a processโor a small group of processesโrunning on the host kernel, isolated by namespaces and constrained by cgroups. This distinction matters for the deployment workflow. There is no boot sequence, no init system, no hypervisor. A container starts in milliseconds, runs until its main process exits, and disappears. Deploying a container means pulling an image, running it with the right configuration, exposing the right ports, mounting the right volumes, and verifying that it works from outside.
This chapter walks through a complete container deployment on a Linux host. You will install a container runtime, run a container from a registry image, verify that it is running and listening, expose it to the network, persist its data with a volume, and inspect it when something goes wrong. The scenario is a typical production task: deploying a web service in a container and making it reachable.
By the end, you will have a repeatable deployment procedure and the diagnostic commands to verify each step.
Key point: A container’s lifecycle is tied to its main process. When the process exits, the container stops. There is no service manager inside the container by default; the runtime ensures the process is running. Persistence lives outside the containerโin volumes, bind mounts, and the image registry. The container itself is disposable.
Why container deployment matters
The packaging problem. An application that runs on a developer’s laptop may fail on a server because of a missing library, a different language version, or an environment variable that was set locally but not in production. A container image packages the application with its runtime, its libraries, and its default configuration. The image is the same everywhere it runs. The only differences are the environment variables and volumes supplied at runtime.
The isolation problem. A container runs in its own namespace. It has its own process tree, its own network stack, its own filesystem view. A container that crashes does not take down the host. A container that is compromised has a limited view of the system. The isolation is not as strong as a virtual machine, but it is sufficient for most workloads, and it is lighter and faster.
The lifecycle problem. A container starts, runs, and stops. It does not persist by default. Files written inside the container are lost when it is removed. This is a feature, not a bug: it means a container can be replaced without losing the application’s state, as long as the state is stored in a volume or an external service. Deployments become replace operations, not patch operations.
The orchestration problem. A single container on a single host is simple. Dozens of containers across multiple hosts require orchestrationโKubernetes, Docker Swarm, Nomad. But orchestration builds on the same primitives: images, containers, volumes, networks. Understanding the single-host deployment is a prerequisite for understanding the orchestrated one.
The LFCA domain coverage. The LFCA exam covers container fundamentals as part of the operations domain . The exam expects candidates to understand what a container is, how it differs from a virtual machine, and the basic commands for managing containers. This chapter provides the practical context for those commands.
a. Installing a container runtime and pulling an image
The container runtime is the software that pulls images, creates containers, and manages their lifecycle. Docker is the most widely used runtime. Podman is a daemonless alternative that is compatible with the Docker CLI. On a Debian or Ubuntu system, Docker is installed from the distribution repository or from Docker’s own repository.
# Debian/Ubuntu
sudo apt update
sudo apt install docker.io -y
# RHEL/Fedora
sudo dnf install podman -y
After installation, verify that the runtime is working.
docker version
docker info
The docker version command shows the client and server versions. The docker info command shows the runtime configuration: storage driver, cgroup driver, number of containers, and more. If the server is not running, docker info reports an error, and the daemon must be started.
sudo systemctl start docker
sudo systemctl enable docker
The daemon must be running for docker commands to work. Enabling it ensures it starts at boot.
With the runtime installed, the next step is pulling an image from a registry. The default registry is Docker Hub. An image is identified by repository:tag.
docker pull nginx:alpine
The nginx:alpine image is the official Nginx image based on Alpine Linux. It is smallโaround 50 MBโand contains the Nginx web server. The pull command downloads the image layers and stores them locally.
docker images
The docker images command lists the locally available images with their repository, tag, image ID, and size. An image that has been pulled once is available for any container that uses it.
b. Running a container with ports and volumes
The docker run command creates and starts a container from an image. Its options configure the container’s runtime environment.
docker run -d \
--name webserver \
-p 8080:80 \
nginx:alpine
The flags:
-druns the container in detached mode (in the background).--name webserverassigns a name to the container. Without it, Docker generates a random name.-p 8080:80maps port 8080 on the host to port 80 in the container. Traffic to the host’s port 8080 is forwarded to the container’s port 80.nginx:alpineis the image to run.
The container starts, and the Nginx process begins listening on port 80 inside the container’s network namespace. The host’s port 8080 is connected to it.
docker ps
The docker ps command lists running containers with their ID, image, command, status, and port mappings. A container that has exited is not shown unless -a is used.
docker ps -a
Persistence is configured with volumes or bind mounts. A volume is managed by Docker and stored in a Docker-managed directory. A bind mount maps a host directory into the container.
docker run -d \
--name webserver \
-p 8080:80 \
-v /srv/www:/usr/share/nginx/html:ro \
nginx:alpine
The -v /srv/www:/usr/share/nginx/html:ro option mounts the host’s /srv/www directory at /usr/share/nginx/html inside the container, read-only. Files placed in /srv/www on the host appear as the web content served by Nginx.
A named volume is created and managed by Docker:
docker volume create webdata
docker run -d \
--name webserver \
-p 8080:80 \
-v webdata:/usr/share/nginx/html \
nginx:alpine
The webdata volume persists independently of the container. If the container is removed and recreated with the same volume, the data is still there.
c. Verification, inspection, and troubleshooting
After the container is running, the first verification is that the process is alive and the port is mapped.
docker ps
The output shows the container ID, image, command, status (e.g., Up 5 minutes), and port mapping (0.0.0.0:8080->80/tcp).
The second verification is that the application responds. From the host:
curl -I http://localhost:8080
The curl -I command sends a HEAD request and prints the response headers. A 200 OK response confirms that Nginx is serving content and the port mapping is working.
From another machine on the network:
curl -I http://server-ip:8080
If the local request works but the remote request fails, the problem is likely the host firewall. Docker manipulates iptables rules to forward traffic to containers, but if the host firewall blocks the port before Docker’s rules apply, the traffic never reaches the container .
sudo ufw status
sudo firewall-cmd --list-all
The firewall must allow the port that is published. On systems using ufw, sudo ufw allow 8080/tcp opens the port. On systems using firewalld, sudo firewall-cmd --add-port=8080/tcp --permanent && sudo firewall-cmd --reload does the same.
When the container is not behaving as expected, the first diagnostic command is docker logs.
docker logs webserver
docker logs -f webserver
The docker logs command shows the stdout and stderr of the container’s main process. The -f flag follows the output in real time. Nginx logs access requests and errors here.
The second diagnostic command is docker inspect.
docker inspect webserver
The docker inspect command returns a JSON document with the container’s configuration: environment variables, mounts, network settings, port mappings, and state. It is the most complete source of information about a container. The --format flag extracts specific fields:
docker inspect --format '{{.State.Status}}' webserver
docker inspect --format '{{.NetworkSettings.IPAddress}}' webserver
docker inspect --format '{{json .Mounts}}' webserver
If the container has exited, docker ps -a shows its exit code. An exit code of 0 means the process completed successfully. A non-zero exit code indicates an error. The logs usually explain why.
docker ps -a
docker logs webserver
To run a command inside a running container, use docker exec.
docker exec -it webserver sh
The -it flags allocate a pseudo-TTY and keep stdin open, providing an interactive shell. Inside the container, you can inspect files, check the network configuration, and run diagnostic commands. The sh command is used because Alpine-based images do not include bash by default .
To stop and remove a container:
docker stop webserver
docker rm webserver
The docker stop command sends SIGTERM to the main process, waits for it to exit gracefully, and sends SIGKILL if it does not. The docker rm command removes the stopped container. The image remains and can be used to start a new container.
Complete Example Session
# ============================================
# PART 1: INSTALL CONTAINER RUNTIME
# ============================================
sudo apt update
sudo apt install docker.io -y
sudo systemctl start docker
sudo systemctl enable docker
# ============================================
# PART 2: VERIFY RUNTIME
# ============================================
docker version
docker info
# ============================================
# PART 3: PULL AN IMAGE
# ============================================
docker pull nginx:alpine
docker images
# ============================================
# PART 4: RUN A CONTAINER
# ============================================
docker run -d --name webserver -p 8080:80 nginx:alpine
docker ps
# ============================================
# PART 5: VERIFY LOCALLY
# ============================================
curl -I http://localhost:8080
# ============================================
# PART 6: CONFIGURE FIREWALL
# ============================================
sudo ufw allow 8080/tcp
sudo ufw status
# ============================================
# PART 7: VERIFY REMOTELY
# ============================================
# On another machine:
curl -I http://server-ip:8080
# ============================================
# PART 8: MOUNT A VOLUME
# ============================================
mkdir -p /srv/www
echo "<h1>Hello from container</h1>" | sudo tee /srv/www/index.html
docker run -d \
--name webserver \
-p 8080:80 \
-v /srv/www:/usr/share/nginx/html:ro \
nginx:alpine
# ============================================
# PART 9: INSPECT AND LOG
# ============================================
docker logs webserver
docker inspect --format '{{.State.Status}}' webserver
docker exec -it webserver sh
# ============================================
# PART 10: STOP AND REMOVE
# ============================================
docker stop webserver
docker rm webserver
docker ps -a
The ten parts covered installing the runtime, verifying it, pulling an image, running a container, local verification, firewall configuration, remote verification, volume mounting, inspection and logs, and stopping and removing the container.
Quick Reference
Container Lifecycle Commands
| Command | Purpose |
|---|---|
docker pull image | Download an image |
docker images | List local images |
docker run -d --name x -p h:c image | Run a container |
docker ps | List running containers |
docker ps -a | List all containers |
docker stop name | Stop a container |
docker rm name | Remove a stopped container |
docker rmi image | Remove an image |
docker run Flags
| Flag | Purpose |
|---|---|
-d | Detached mode |
--name | Assign a container name |
-p host:container | Publish a port |
-v host:container | Bind mount |
-v volume:container | Named volume |
-e KEY=value | Set environment variable |
--restart | Restart policy |
Diagnostic Commands
| Command | Purpose |
|---|---|
docker logs name | Container stdout/stderr |
docker logs -f name | Follow logs |
docker inspect name | Full container config |
docker exec -it name sh | Shell inside container |
docker stats | Live resource usage |
docker top name | Processes in container |
Port and Firewall
| Task | Command |
|---|---|
| Publish port | -p 8080:80 |
| Check listening | ss -tulpn | grep 8080 |
| Open UFW port | sudo ufw allow 8080/tcp |
| Open firewalld port | sudo firewall-cmd --add-port=8080/tcp |
| Check Docker iptables | sudo iptables -t nat -L |
Best Practices
โ Do This:
# Use specific image tags, not :latest
docker pull nginx:1.27-alpine # โ
# Name your containers
docker run -d --name webserver -p 8080:80 nginx:alpine # โ
# Mount data as volumes, not inside the container
docker run -v /srv/www:/usr/share/nginx/html:ro ... # โ
# Verify locally before verifying remotely
curl -I http://localhost:8080 # โ
# Check logs when the container is not working
docker logs webserver # โ
# Use a restart policy for production
docker run -d --restart unless-stopped ... # โ
# Open the firewall port explicitly
sudo ufw allow 8080/tcp # โ
โ Don’t Do This:
# Don't run containers without a name in production
docker run -d nginx:alpine # โ ๏ธ random name
# Don't store data inside the container
# (it is lost when the container is removed) # โ
# Don't expose unnecessary ports
docker run -p 0.0.0.0:3306:3306 mysql # โ
# Don't skip the firewall check
# (local works, remote fails) # โ
# Don't use :latest in production
docker pull nginx:latest # โ ๏ธ
# Don't exec into a container to make persistent changes
docker exec -it webserver sh
# Changes are lost when the container is recreated # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Remote access fails | Host firewall blocking | ufw allow 8080/tcp |
| Data lost on container removal | Data written inside container | Use volumes |
| Container exits immediately | Main process exits | Check docker logs |
| Port already in use | Another process on host port | Use different host port |
| Image not found | Wrong tag or registry | Check docker pull output |
| Cannot exec with bash | Alpine has no bash | Use sh |
| Container not restarting | No restart policy | Add --restart |
Real-World Examples
1. Run Nginx
docker run -d --name web -p 8080:80 nginx:alpine
2. Mount Web Content
docker run -d -v /srv/www:/usr/share/nginx/html:ro nginx:alpine
3. Set Environment Variable
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8
4. Persistent Volume
docker volume create dbdata
docker run -d -v dbdata:/var/lib/mysql mysql:8
5. Restart Policy
docker run -d --restart unless-stopped nginx:alpine
6. Check Logs
docker logs -f web
7. Inspect Container
docker inspect --format '{{.NetworkSettings.IPAddress}}' web
8. Shell Inside Container
docker exec -it web sh
9. List All Containers
docker ps -a
10. Remove Container and Image
docker stop web && docker rm web
docker rmi nginx:alpine
Visual
Container vs Virtual Machine
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ VIRTUAL MACHINE โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Application โ โ
โ โ Libraries โ โ
โ โ Guest OS (full kernel) โ โ
โ โ Hypervisor โ โ
โ โ Host OS โ โ
โ โ Hardware โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Boots in seconds. Full OS per VM. Heavy. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ CONTAINER โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Application โ โ
โ โ Libraries โ โ
โ โ (shares host kernel) โ โ
โ โ Container runtime โ โ
โ โ Host OS โ โ
โ โ Hardware โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Starts in milliseconds. Shares kernel. Lightweight. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Container Lifecycle
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CONTAINER LIFECYCLE โ
โ โ
โ docker pull nginx:alpine โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโ โ
โ โ IMAGE โ (read-only, stored locally) โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ docker run โ
โ โผ โ
โ โโโโโโโโโโโโโโโ โ
โ โ CONTAINER โ (writable layer on top of image) โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ process exits โ
โ โผ โ
โ โโโโโโโโโโโโโโโ โ
โ โ STOPPED โ (container still exists) โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ docker rm โ
โ โผ โ
โ โโโโโโโโโโโโโโโ โ
โ โ REMOVED โ (writable layer discarded, image remains) โ
โ โโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Port Mapping
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PORT MAPPING: -p 8080:80 โ
โ โ
โ Host Network Namespace โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ :8080 โโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โ Docker port forwarding โ
โ โ โ
โ Container Network Namespace โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ :80 โโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ nginx process listening on port 80 โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Client connects to host:8080 โ forwarded to container:80 โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Volume vs Bind Mount
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BIND MOUNT: -v /srv/www:/usr/share/nginx/html โ
โ โ
โ Host: /srv/www โ
โ โ โ
โ โโโโถ Container: /usr/share/nginx/html โ
โ โ
โ Host directory is mapped directly into the container. โ
โ Files created on either side are visible on both. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ NAMED VOLUME: -v webdata:/usr/share/nginx/html โ
โ โ
โ Docker-managed storage โ
โ โ โ
โ โโโโถ Container: /usr/share/nginx/html โ
โ โ
โ Volume is managed by Docker. โ
โ Persists independently of the container. โ
โ Path on the host is under /var/lib/docker/volumes/. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Runtime install | apt install docker.io or dnf install podman |
| Daemon start | systemctl start docker |
| Pull image | docker pull nginx:alpine |
| Run container | docker run -d --name web -p 8080:80 nginx:alpine |
| List running | docker ps |
| List all | docker ps -a |
| Port mapping | -p host:container |
| Bind mount | -v /host:/container |
| Named volume | -v volume:/container |
| Logs | docker logs name |
| Inspect | docker inspect name |
| Exec | docker exec -it name sh |
| Stop | docker stop name |
| Remove | docker rm name |
Key takeaways:
- A container is a process, not a virtual machine. It shares the host kernel and is isolated by namespaces and cgroups. It starts in milliseconds and exits when its main process exits. There is no init system inside the container.
- Container data is ephemeral unless stored in a volume. Files written inside the container are lost when the container is removed. Volumes and bind mounts provide persistence.
- Port mapping connects the host to the container.
-p 8080:80forwards traffic from the host’s port 8080 to the container’s port 80. The container’s network namespace is separate from the host’s. - The host firewall can block published ports. Docker manipulates iptables rules to forward traffic to containers, but if the host firewall blocks the port first, the traffic never reaches Docker’s rules. Open the port explicitly with
ufworfirewalld. docker logsis the first diagnostic command. It shows the stdout and stderr of the container’s main process. When a container exits immediately or behaves unexpectedly, the logs usually explain why.docker inspectshows the full configuration. It returns a JSON document with the container’s state, environment, mounts, network settings, and port mappings. The--formatflag extracts specific fields.docker execruns a command inside a running container. It is useful for inspecting the container’s filesystem and network configuration. Alpine-based images usesh, notbash.- Use specific image tags, not
:latest. The:latesttag is mutable and can change without warning. A specific tag likenginx:1.27-alpineensures the same image is used every time.
Remember: Deploying a container is a deployment, not just a docker run. The command starts a process, but the deployment includes the port mapping, the volume configuration, the firewall rules, and the verification that the service is reachable from outside. The container is disposableโit can be removed and recreated without losing the application’s state, as long as the state lives in a volume or an external service. This is the fundamental difference from a virtual machine, and it is what makes containers well-suited to automated deployment. Verify each step: the container is running, the port is mapped, the service responds locally, and the service responds remotely. When something fails, the logs and the inspect output tell you why.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!