Learn Docker - Running Your First Container & CLI Fundamentals
Episode 3 of 28

Learn Docker - Running Your First Container & CLI Fundamentals

First hands-on practice: running hello-world and ubuntu containers, understanding the interactive options (-i, -t, --rm, --name), managing containers with ps, stop, start, and rm, interacting via exec and logs, and port mapping to connect containers to the outside world.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Introduction

After dissecting Docker's architecture in episode 2 — client, daemon, containerd, runc, and the four core components (Image, Container, Volume, Network) — in this episode we get straight to practice. Time to get our hands dirty and run our first container.

Don't underestimate this episode. docker run, docker ps, docker exec, docker logs, and port mapping are commands you'll type hundreds of times a day in the workplace. Engineers who've used Docker for years still live on these commands — the difference is, they understand the meaning behind each option rather than just memorizing them. This episode builds that habit: every command is run while understanding what's happening behind the scenes — following the architecture map we built in episode 2.

In this episode we'll run our first containers (hello-world and ubuntu), understand the interactive options -i, -t, --rm, --name, manage containers with ps, stop, start, rm, interact from inside the container with exec and logs, and close with port mapping that connects containers to the outside world.

Main Discussion

Your First Container: hello-world

Open a terminal and run:

Kontainer pertama kalian
docker run hello-world

If this is the first time, Docker will automatically pull the hello-world image from Docker Hub, then run it. Notice the following output:

Contoh output docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
[...]
Hello from Docker!
This message shows that your installation appears to be working correctly.

Two important things happen here:

  1. Automatic pull. The hello-world image isn't in the local cache, so the daemon downloads it from Docker Hub, then runs it. On the second execution, the image is already cached — the pull is skipped and the container runs immediately. This is the same flow for every image: "use image → check cache → pull if missing".
  2. This container runs, prints its message, then exits. hello-world is a small binary that only prints text and then stops. Its final status is Exited — and that's normal, not an error. A container always stops when its main process finishes.

Also note the image naming convention: hello-world:latest consists of the image name (hello-world) and the tag (latest, the default when none is specified). Tags select the image version — we'll dig deeper in episode 12.

Running an Interactive Shell: -i, -t, --rm, --name

Containers aren't always "print then exit". Many images — like ubuntu — contain a complete system with no meaningful default command. To use them interactively:

Masuk ke shell Ubuntu dalam kontainer
docker run -it --rm --name my-ubuntu ubuntu bash

Let's break down these options — this is what beginners most often misunderstand:

OptionFunctionWhy
-i (interactive)Keeps STDIN openLets you type into the container
-t (tty)Allocates a pseudo-terminalDisplays clean output, a shell prompt, color support
--rmAuto-removes when the container stopsPrevents "zombie" containers from piling up in ps -a
--nameNames the containerReplaces the random ID; makes stop/logs/exec easier

Inside that shell, you're in an isolated Ubuntu environment — with PID 1 as the bash process, your own filesystem, and only minimal packages. Try cat /etc/os-release, ls /, and exit to leave. Because we used --rm, the container is cleaned up as soon as the shell closes.

Tip

The -it combination is always used together for interactive sessions: -i alone without -t produces output without a comfortable prompt, while -t alone without -i makes input feel "stuck". For an interactive shell, both must go together. However, to run a one-off command, just docker run ubuntu echo "hai" — without -it.

Managing Containers: ps, stop, start, rm

Now let's leave the interactive session and learn to manage containers running in the background. Start two containers:

Jalankan nginx secara detached
docker run -d --name my-web nginx

The -d (detached) option runs the container in the background — the terminal returns immediately. To see running containers:

Daftar kontainer aktif dan semua kontainer
docker ps
docker ps -a
Contoh output docker ps
CONTAINER ID   IMAGE     COMMAND                  PORTS      NAMES
1f3a9c7b2d5e   nginx     "/docker-entrypoint.…"   80/tcp     my-web

The difference between docker ps and docker ps -a: the first only shows running containers (status Running), while the second shows all containers including stopped ones (Exited). You'll often find Exited containers of unclear origin — those are what we'll clean up in episode 4.

The most basic lifecycle management:

Stop, start, dan hapus kontainer
docker stop my-web
docker start my-web
docker stop my-web
docker rm my-web
  • docker stop sends a SIGTERM signal (with a grace period, 10 seconds by default) so the main process stops cleanly — it doesn't force-kill.
  • docker start brings an existing container back to life (its state is preserved).
  • docker rm deletes a container that has already stopped. To delete a running container, add -f (force) — Docker sends SIGKILL and then removes it.
Error jika menghapus kontainer yang masih berjalan
$ docker rm my-web
Error response from daemon: You cannot remove a running container ...

Finding and Filtering Containers

The more containers you manage, the more important filtering becomes. Two docker ps options you must master:

Filter daftar kontainer
docker ps -a --filter "status=exited"
docker ps --filter "name=my-"
docker ps -q
  • --filter "status=exited" shows only stopped containers — a good starting point for cleanup.
  • --filter "name=my-" searches containers by name prefix — useful when you forget the exact name.
  • -q (quiet) shows only container IDs, one per line. This is the raw material for scripting: imagine docker rm $(docker ps -aq --filter "status=exited") — one line that deletes all stopped containers at once. This $(...) pattern will become a close friend in the episodes ahead.

One key concept: a container lives only as long as its main process (PID 1 inside the container) is running. When you run docker run -d --name my-web nginx, the nginx image has a default command that runs the nginx server in the foreground — as long as nginx is alive, the container is Up. If that process finishes (or crashes), the container immediately goes Exited. This is why docker run -d ubuntu bash appears to "die instantly": bash without interactive arguments exits right away, and with it the container stops. The main process is the heart of the container.

Interacting from Within: exec and logs

A detached container can still be "entered" and read from the outside — without disturbing its main process. Two commands you'll use most:

1. docker exec — run a new command inside a running container.

Masuk ke shell dalam kontainer nginx
docker run -d --name my-web nginx
docker exec -it my-web bash

Note the difference from docker run: run creates a new container; exec enters a container that's already running — just like knocking on the container's door and walking in. Inspect the files inside, for example ls /usr/share/nginx/html, then exit. The nginx image is Debian-based, so bash is available; for the slimmer Alpine images, use sh.

2. docker logs — read the container's output (stdout/stderr).

Baca log kontainer
docker logs my-web
docker logs --tail 20 my-web
docker logs -f my-web
  • Without options: the full log.
  • --tail N: only the last N lines — saves output for long logs.
  • -f (follow): follows the log in real time, like tail -f — very useful when debugging.

Warning

An important rule of thumb: don't install SSH inside a container. A container is designed for a single main process; entering via docker exec is the correct and far lighter approach. If you feel the need for SSH, you're using the wrong pattern — a container that's "like a server" is usually a sign of poor design.

Port Mapping: Connecting Containers to the Outside World

Containers run on an internal network with their own IP that can't be reached directly from the host. To connect them to the outside world, you must map ports with the -p host_port:container_port option:

Petakan port 8080 host ke port 80 kontainer
docker run -d --name web -p 8080:80 nginx

Now access it from the host:

Verifikasi dengan curl atau buka di browser
curl -I http://localhost:8080
Output curl -I
HTTP/1.1 200 OK
Server: nginx/1.27.3
Content-Type: text/html

Port mapping analogy: the container is a restaurant serving its food from a kitchen numbered 80; you install a service door numbered 8080 on the main road, and every order coming in through door 8080 is forwarded to kitchen 80. Open http://localhost:8080 in your browser — you'll see the nginx welcome page. Without -p, the container still runs but can't be reached from the host — a fact that often confuses beginners.

Note how the mapping works: host ports must be unique — you can't have two containers mapping -p 8080:80 at the same time. The port is already allocated error appears when they clash; the fix is to change the host port (e.g. -p 8081:80) or stop the container using it.

Comparing Ways to Run Containers

A summary of all the approaches above in one code-group — pick according to your needs:

docker run -it --rm --name app-dev ubuntu bash

These three patterns cover almost every need: foreground for interactive experiments and debugging, detached for services that must keep running, one-shot for short jobs like running a utility without leaving a trace. Choose the pattern based on what you're doing, not out of habit.

Common Pitfalls

  1. Forgetting -it for interactive sessions. docker run ubuntu bash appears to "hang" — in reality bash runs without open input. Always use -it for an interactive shell.

  2. Forgetting port mapping. docker run -d nginx succeeds, but curl localhost:80 fails. The container runs on an internal network — without -p, there's no way in from the host.

  3. Using a clashing host port. docker run -p 8080:80 nginx fails with port is already allocated. Check with ss -tlnp or change the host port.

  4. Deleting a running container. docker rm refuses active containers. Stop it first (docker stop), or use -f deliberately.

  5. "Big" images during demos. docker run ubuntu pulls hundreds of MB because the Ubuntu image is a full OS. For light experiments, images like alpine (a few MB) are much faster.

MistakeSymptomSolution
Missing -itShell "hangs" without a promptUse -it for interactive sessions
Missing -pContainer runs but can't be reachedMap -p host:container
Port clashport is already allocatedChange the host port / stop the user
rm on an active containerError "cannot remove running"stop first, or rm -f

Note

Layered debugging: if a service doesn't respond, check in logical order — is the container still running? (docker ps) → is the main process running? (docker exec -it web bash then ps aux) → is the port actually listening? (docker logs web and ss -tlnp on the host). This sequence solves 90% of "can't reach the container" problems.

Conclusion

In episode 3 you've run your first container and mastered the Docker CLI fundamentals: docker run hello-world with the automatic pull flow, docker run -it ubuntu bash with an understanding of the -i, -t, --rm, and --name options, container management with docker ps / ps -a, stop, start, rm (including -f), interaction from within via docker exec and docker logs (-f, --tail), and port mapping -p host_port:container_port that connects containers to the outside world.

The core takeaways:

  • docker run = pull if needed + create + run; a container stops when its main process exits.
  • -i (input stays open) + -t (pseudo-terminal) = interactive session; --rm for disposable containers; --name for identity.
  • docker ps = active ones; docker ps -a = all; stop (SIGTERM) ≠ kill (SIGKILL).
  • exec enters a running container; logs reads its stdout/stderr.
  • Without -p, a container runs but can't be reached from the host.

These commands are your "hands" — and you've just trained them. In the next episode, episode 4, we'll go deeper into the container lifecycle and state management: the state machine from Created → Running → Paused → Stopped → Exited → Dead, the docker create, pause/unpause, restart, kill commands, inspecting JSON metadata with docker inspect, real-time monitoring with top, stats, and events, and system cleanup with docker system prune. See you in episode 4!

Learn Docker - Running Your First Container & CLI Fundamentals | Learn Docker