Skip to main content

Docker basics

This guide covers the essential Docker commands you'll use day to day: what images and containers are, how to run them, how to persist their data, and how to clean up afterwards. It assumes you already have Docker installed. See Docker installation on Ubuntu 20.04+ if you haven't.

Images vs. containers​

The two core concepts in Docker:

  • Image: a read-only template that bundles an application and everything it needs to run (its own filesystem, libraries, and default command). Images are built once and can be reused many times.
  • Container: a running instance of an image. You can start, stop, and delete containers freely without affecting the image they came from. You can run as many containers as you like from a single image.

Most images come from Docker Hub, Docker's public image registry. When you run an image you don't have locally, Docker downloads (pulls) it automatically.

Pulling and running containers​

To download an image without starting it:

docker pull nginx

To run a container from an image:

docker run -d --name web -p 8080:80 nginx

The most important flags for docker run:

FlagMeaning
-dDetached mode: run the container in the background instead of attaching your terminal to it
--name webGive the container a readable name instead of a random one
-p 8080:80Publish a port: map port 8080 on the host to port 80 inside the container
-e KEY=valueSet an environment variable inside the container
-v webdata:/dataMount a volume at /data inside the container
--restart unless-stoppedAutomatically restart the container on boot or after a crash

After executing the command above, Nginx is reachable at http://localhost:8080 because the container's port 80 is mapped to the host's port 8080.

Inspecting containers​

List running containers:

docker ps

List all containers, including stopped ones:

docker ps -a

The output looks something like this:

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp, :::8080->80/tcp web

Other useful inspection commands:

docker logs web # show the container's log output
docker logs -f web # follow the logs, like tail -f
docker inspect web # full details: IP address, mounts, env vars, exit code
docker stats # live CPU / memory / network usage per container
docker top web # processes running inside the container

To run a command inside a running container, for example a shell:

docker exec -it web bash

The -it flags allocate an interactive terminal, so you land in a bash prompt inside the container. Type exit to leave. The container keeps running the whole time.

Starting, stopping, and removing​

docker stop web # gracefully shut down the container
docker start web # start a stopped container again
docker restart web # stop then start
docker kill web # force-stop immediately (use only if stop hangs)

A stopped container still exists on disk and can be restarted at any time. To delete it permanently:

docker rm web

You can remove a running container without stopping it first by adding -f:

docker rm -f web
caution

Deleting a container deletes anything written inside it that is not on a volume or bind mount. See the next section.

Persisting data with volumes​

By default, a container's filesystem is temporary: when you remove the container, any data written inside it is gone. For anything you care about (databases, app configs, media libraries) mount storage from the host.

Named volume (managed by Docker; the easiest option for most services):

docker run -d --name db -v dbdata:/var/lib/mysql mariadb

The dbdata volume is created automatically on first use and survives container deletion. List and manage volumes with:

docker volume ls
docker volume inspect dbdata

Bind mount (map a specific host directory; useful for config files or app data you want to browse directly):

docker run -d --name web -v /srv/nginx-conf:/etc/nginx/conf.d:ro nginx

The optional :ro suffix mounts the directory read-only inside the container.

Tip: In homelab setups, named volumes are great for databases while bind mounts are handy for directories you back up or edit by hand, such as config folders on a NAS share.

Docker Compose​

Typing long docker run commands for multi-container apps gets unwieldy fast. Compose lets you describe an app (its containers, ports, volumes, and networks) in a YAML file called compose.yaml and manage it with one command.

Example: a Minecraft server with a persistent data directory.

services:
minecraft:
image: itzg/minecraft-server
container_name: minecraft
restart: unless-stopped
ports:
- "25565:25565"
environment:
EULA: "TRUE"
MEMORY: "4G"
volumes:
- ./data:/data

Put that file in a directory, then:

docker compose up -d # create and start everything defined in the file
docker compose ps # list the app's containers
docker compose logs -f # follow logs of all services
docker compose pull # update images to their latest versions
docker compose up -d # recreate containers with the new images
docker compose down # stop and remove the containers (volumes are kept)

docker compose down -v also removes the named volumes defined in the file, so make sure you really want to delete the data before using it.

Housekeeping​

Docker happily keeps everything forever, so disks can fill up over time. Useful cleanup commands:

docker system df # show how much space images/containers/volumes use
docker container prune # remove all stopped containers
docker image prune # remove untagged "dangling" images
docker image prune -a # remove all images not used by any container
docker volume prune # remove volumes not attached to any container
docker system prune # combine the prunes above (asks for confirmation)
docker system prune -a --volumes # everything, including unused volumes
caution

docker volume prune deletes any volume no container is using, including the data inside it. Double-check with docker volume ls first.

Further reading​