Concepts
Three core terms you meet constantly: an image is an immutable template (code + dependencies), a container is a running instance of an image, and Compose describes one or more containers declaratively in a YAML file so the whole stack starts with a single command.
Installation
Ubuntu / DebianUse Docker's official apt repo rather than the distro package — it's newer and includes Compose v2 as a plugin.
sudo apt remove docker docker-engine docker.io containerd runc # if present
sudo apt update
sudo apt install ca-certificates curl gnupg -y
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$VERSION_CODENAME isn't recognized by the repo, set it manually to the Ubuntu/Debian base your version is built on (e.g. noble or bookworm).sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin -y
sudo docker run --rm hello-world
docker compose version
Post-install
Run Docker without sudo and let the daemon start at boot.
sudo usermod -aG docker $USER
# log out/in or:
newgrp docker
sudo systemctl enable --now docker
docker group is effectively equivalent to root access (you can mount the host file system into a container). Grant access only to trusted users, and consider rootless mode on multi-user systems.Docker Compose
deklarativtDescribe your stack in compose.yaml and control it with docker compose. Example skeleton:
services:
app:
image: nginx:latest
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
volumes:
- ./data:/usr/share/nginx/html:ro
networks: [ web ]
networks:
web:
| Kommando | Funktion |
|---|---|
docker compose up -d | Start the stack in the background (detached) |
docker compose down | Stop and remove containers + networks |
docker compose pull | Pull the latest images |
docker compose logs -f | Follow logs live |
docker compose ps | Status of the stack's services |
docker compose restart | Restart services |
docker compose exec app sh | Open a shell in a running container |
docker compose pull && docker compose up -d pulls new images and recreates only the containers that changed.CLI reference
| Kommando | Funktion |
|---|---|
docker ps / docker ps -a | Running / all containers |
docker images | List local images |
docker logs -f NAME | Follow a container's logs |
docker exec -it NAME sh | Interactive shell in a container |
docker stop / start / restart NAME | Lifecycle management |
docker rm NAME / docker rmi IMAGE | Remove container / image |
docker inspect NAME | Full JSON metadata (network, mounts, config) |
docker stats | Live CPU/RAM/IO per container |
docker system df | Disk usage broken down by images/containers/volumes |
docker system prune -a | Clear unused images/containers/networks (frees space) |
prune -a: It removes all images not in use by a container — including ones you want to keep. Add --volumes only if you deliberately want to delete unmounted volumes (can mean data loss).Networking
Containers on the same user-defined network can reach each other via service name as hostname — that's how n8n finds its database.
| Type | Use |
|---|---|
| bridge (default) | Default isolated network per Compose stack; service name = DNS |
| host | Shares the host network stack directly (no isolation — avoid if possible) |
| none | No networking — full isolation |
| internal | Bridge without outbound internet — good for databases |
internal network so it can't reach the internet, and the application on both the internal and a proxy network. Never expose the database port on the host.Volumes & persistence
Containers are ephemeral — data that must survive a recreation has to go in a volume or bind mount.
docker volume create n8n_data
docker volume ls
docker volume inspect n8n_data
# in compose: ./data:/app/data
# gives you direct access to the files on the host
/var/lib/docker/volumes/. For your restic/pCloud stack you can either back up that path, or better: docker compose exec db pg_dump … for consistent database dumps rather than copying raw files while the database is running.Hardening
| Control | Recommendation |
|---|---|
| Port-binding | Bind to 127.0.0.1, expose only via a reverse proxy |
| restart-policy | unless-stopped so services come back up after reboot |
| Image-tags | Pin to specific versions, not blindly latest, in production |
| read-only | read_only: true + tmpfs where possible |
| capabilities | cap_drop: [ALL] and add back only what is needed |
| no-new-privileges | security_opt: [no-new-privileges:true] |
| UFW | Note: Docker manipulates iptables and can bypass UFW rules — bind to localhost rather than relying on UFW alone |
| Opdatering | Regular pull + recreate; monitor base image CVEs |
0.0.0.0, it's often open to the internet regardless of UFW. The fix: always bind to 127.0.0.1:PORT for internal services, or use the ufw-docker project to correct the rule ordering.