Docker (Compose) Cheat Sheet
This is a collection of tips and tricks I picked up while learning and working with Docker and Docker Compose on my home server and web server.
Container Configuration
Environment Variables
Where to Define Environment Variables
- Environment variables are a common way to configure containers. To keep things organized, don’t put them in your Compose file but into dedicated files with the extension
env
.
enf_file vs. .env
.env
file: this “special” file can be used to set environment variable for use in the Compose file. The variables specified in.env
are not available in the container.env_file
: this section in the Docker Compose file lets you specify files that contain environment variables for use in the container. The variables specified in this section are not available in the Compose file.
Bind Mounts vs. Docker Volumes
- Bind mounts let you control the directory structure.
- This has the advantage that you know exactly what gets stored where in the host’s file system.
- It has the disadvantage that you need to create the directory structure before you can start a container.
- Docker volumes are managed by the Docker engine.
- They’re stored in
/var/lib/docker
, “far away” from the Compose file.
- They’re stored in
- Personally, I very much prefer bind mounts because of the control they offer.
- I use subdirectories relative to the Compose file, e.g.,
./data:/data
. - Keeping the container configuration and the container data in one place facilitates backups.
- I use subdirectories relative to the Compose file, e.g.,
Networking
Expose vs. Ports
- Expose serves as documentation which ports a container is accessible on.
- Note: container ports are always accessible from other containers on the same Docker network.
- Ports makes container ports accessible to the host.
- Most of my services are accessible through the Caddy reverse proxy only.
- Opening ports to the host is, therefore, only rarely necessary.
Static IP Address on the Host Network
- Use the Macvlan Docker network to attach a container directly to the host’s local network.
- Assign a static IP address by specifying the
ip_range
parameter in theipam
section of the Docker Compose file. - See this configuration for an example.
Disable Macvlan Container/Host Isolation
- Containers on a Macvlan network are isolated from the host.
- While the container can contact other machines on the local network, communications with the host are blocked.
- To work around that, create a virtual link with a route that points to the container’s IP address (example).
Time Zone
- Containers should know about your local time zone. To achieve that, make it a habit to pass in
/etc/localtime
as a read-only volume to every container:volumes: - /etc/localtime:/etc/localtime:ro
Container User & Group IDs (UID/GID)
- Well-designed containers don’t run as root (UID/GID = 0) but with dedicated user and group IDs.
- The container’s UID and GID need to be granted access to bind-mounted volumes on the host (volumes from the host that are passed into the container) as follows:
chown -Rfv UID:GID /path/to/bind/mounted/volume/on/Docker/host
Compose File Version
- Many Compose files you see on the internet specify a version as the first line. That is deprecated. Remove it. Example:
version: '3.9'
Container Operations
Container App Logs
- Logs from the containerized application are essential for troubleshooting. Use the following command on the host to list the last 100 log lines with timestamps:
docker compose logs --tail 100 --timestamps
- If your Docker Compose file contains definitions for multiple services you may want to restrict the log to only one container as follows:
docker compose logs CONTAINER_NAME --tail 100 --timestamps
Shell Access to & Running Commands in Container
How to Run Commands in the Container
- It can be necessary to run commands in the container from the host.
- This can be achieved with
docker exec
. - Example from one of my many articles featuring Caddy:
docker exec -w /etc/caddy caddy caddy reload
- The above command specifies and optional working directory (
-w /etc/caddy
), running thecaddy
command in thecaddy
container with the parameterreload
.
How to Invoke an Interactive Shell in the Container
- It can be very helpful to invoke an interactive shell in a running container.
- This can be achieved with
docker exec -it
. - Example from my WordPress Docker article:
docker exec -it mariadb bash
- The above command interactively starts the
bash
shell in themariadb
container. - Obviously, the container must come with bash as a shell.
- Containers based on Alpine Linux provide
ash
and/orsh
instead.
Container Data Backup
To back up container data, I employ one of the following two strategies:
- Stop all Docker services:
- Stopping all containers and related Docker services ensures that there are no open file handles and that databases are in a consistent state.
- Once everything has been stopped, bind-mounted volumes can be backed up on the host.
- You can find an example for this strategy in my restic home server article.
- Dump the database:
- If you don’t want to incur downtime, you can dump databases prior to the backup.
- In this case you don’t back up the data directory of the dockerized database but the dump file instead.
- You can find an example for this strategy in my restic web server article.
Delaying the Docker Service Start Until Volumes Are Ready
- Scenario: Your container configuration or data files are stored on a volume that may not be available yet when the Docker service is started during system boot.
- This is the case, for example, if the Docker data resides on an encrypted ZFS volume that needs to be unlocked after a reboot.
- Solution: Delay the Docker service start with a unit file snippet that waits for the ZFS volume to become available (example).