TechWorld with Nana DevOps Bootcamp — Module 7
First time running Docker commands hands-on.
Docker on Windows is split into two pieces: the CLI (the docker command in PowerShell)
and the Engine which lives inside Docker Desktop. The engine must be running before any
command works. First command I ran:
docker --version
docker infoFirst mistake: got "Cannot connect to Docker daemon" — Docker Desktop wasn't open yet. Fixed by opening Docker Desktop from the Start menu and waiting for the whale icon to stop animating. Set it to start on login so this never happens again.
docker pull redis:7.2-alpine
docker imagesKey learning: 7.2-alpine is the tag. Without it Docker uses latest which is
unpredictable — the version can change without you knowing and break your app. The
alpine part means it's built on Alpine Linux (~5MB base OS) which is why the image
is only 41MB vs ~130MB+ for the full version.
Each "Pull complete" line in the output is a separate image layer downloading. Images are built in stacked layers — base OS at the bottom, application on top.
# Attached — terminal is locked showing live logs. Ctrl+C to stop.
docker run redis:7.2-alpine
# Detached — runs in background, terminal stays free
docker run -d -p 6380:6379 --name my-redis redis:7.2-alpine
# Confirm it's running
docker psMistake made: forgot --name flag on first try. Docker treated my intended name as
the image name and tried to pull it from Docker Hub. Error: "pull access denied".
Fix: always put --name before the image name. The image always goes last.
Port binding format is always -p HOST:CONTAINER. You always use the host port
from your browser.
This is the demo that makes Docker's value instantly obvious. Without Docker you'd need complex configuration to run two Postgres versions on one machine. With Docker:
docker run -d -p 5432:5432 --name pg15 -e POSTGRES_PASSWORD=secret postgres:15-alpine
docker run -d -p 5433:5432 --name pg16 -e POSTGRES_PASSWORD=secret postgres:16-alpine
docker pspg16 uses host port 5433 to avoid colliding with pg15 on 5432. Both containers think they're on port 5432 internally — neither knows the other exists. Each is completely isolated.
The -e flag sets environment variables inside the container. Postgres refuses to
start without POSTGRES_PASSWORD — it's a required variable documented on Docker Hub.
# Get a shell inside the redis container
docker exec -it my-redis /bin/sh
# Inside the shell:
ls # saw dump.rdb — the Redis backup file
env # read all environment variables
cat /etc/os-release # confirmed Alpine Linux 3.21
exitKey things learned from reading the env output:
HOSTNAME= the container ID — becomes the hostname on Docker networksREDIS_DOWNLOAD_SHA= a SHA-256 checksum Docker used to verify the Redis binary wasn't tampered withGOSU_VERSION= a tool that drops from root to a less privileged user on startup (security)HOME=/root= container runs as root (fine for learning, bad practice in production)- Use
/bin/shfor alpine images,/bin/bashfor Ubuntu/Debian images
Mistake: tried to run docker exec while still inside the redis container shell.
Docker doesn't exist inside a container — it's a host machine command. Had to exit
first.
docker exec -it pg15 psql -U postgres\l -- list databases
CREATE DATABASE testdb;
\c testdb
CREATE TABLE users (id SERIAL, name TEXT);
INSERT INTO users (name) VALUES ('HARRY');
SELECT * FROM users;
\qMistake: typed INSERT INTO user (missing the s). user is a reserved word in
Postgres so the error was "syntax error at or near 'user'" rather than "table not found".
Key insight: you don't need psql installed on Windows. It's already inside the
Postgres container. docker exec -it gets you to it.
docker logs my-redis # all past logs
docker logs -f my-redis # stream live (Ctrl+C exits, container keeps running)
docker logs --tail 20 my-redis # last 20 lines onlyThe line to look for in any database container: the "ready" message.
- Redis:
Ready to accept connections tcp - Postgres:
database system is ready to accept connections - MongoDB:
Waiting for connections
If you never see that line, something crashed before it finished starting.
When I pressed Ctrl+C on the attached Redis container I saw a graceful shutdown:
Received SIGINT → User requested shutdown → Saving RDB snapshot → DB saved → bye bye
This is clean. Compare to docker kill which sends SIGKILL — instant forced termination
with no chance to save, potential data loss.
docker ps # running containers only
docker ps -a # ALL containers including stopped
docker stop <name> # graceful stop
docker start <name> # restart existing container (NOT docker run)
docker rm <name> # remove stopped container
docker rm -f <name> # force remove running container
docker rmi <image:tag> # remove image
docker container prune # remove all stopped containers
docker system df # see how much disk Docker is usingrun vs start: docker run ALWAYS creates a new container. docker start restarts
an existing stopped one. If you see 10 stopped containers with the same image name you
ran when you should have started.
Partial IDs work, partial names don't: docker stop a02d works.
docker stop my-red fails if the container is named my-redis.
Exit code 137 = killed by SIGKILL = almost always out of memory.