Build a Raspberry Pi Container Host with Docker Compose
Build a maintainable Raspberry Pi container host with Docker Engine, the current Compose plugin, service health checks, and a controlled update workflow.
Expected Outcome
A 64-bit Raspberry Pi OS host running Docker Engine and the Compose plugin, with a small Nginx service that can be validated, updated deliberately, and backed out by restoring a known-good Compose definition and image reference.
Assumptions
Raspberry Pi 3, 4, or 5 running a current 64-bit Raspberry Pi OS release; verify `dpkg --print-architecture` returns `arm64` before using the Debian repository commands below.
SSH or local console access with a user that can run `sudo`.
Working DNS and internet access from the Raspberry Pi.
A maintenance window for package installation and container restarts.
Bill of Materials
Raspberry Pi with a reliable power supply and supported storage.
Current 64-bit Raspberry Pi OS installation.
Network connectivity to Docker's Debian package repository and Docker Hub.
A workstation or terminal for SSH access and a place to record validation and rollback evidence.
Build Steps
- Confirm the operating-system architecture
Use the 64-bit path for a current long-lived build. Docker directs 64-bit Raspberry Pi OS users to the Debian arm64 packages. If this host reports armhf, stop and use Docker's Raspberry Pi OS 32-bit guidance instead; Docker Engine v28 is the final major release with official Raspberry Pi OS 32-bit packages.
Read-only command: verify target and scope
dpkg --print-architecture cat /etc/os-release
- Update the host and install repository prerequisites
Bring the package index current and install the packages Docker uses to configure its signed apt repository. Review pending upgrades before applying them on an existing host.
Changes system state: review before running
sudo apt update apt list --upgradable sudo apt install -y ca-certificates curl
- Add Docker's official Debian apt repository
Configure Docker's signed Debian repository rather than piping the convenience installer directly into a privileged shell. Docker documents the convenience script as a testing/development option; the repository path is easier to maintain and upgrade deliberately.
Changes system state: review before running
sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc . /etc/os-release && printf 'Types: deb\nURIs: https://download.docker.com/linux/debian\nSuites: %s\nComponents: stable\nArchitectures: %s\nSigned-By: /etc/apt/keyrings/docker.asc\n' "$VERSION_CODENAME" "$(dpkg --print-architecture)" | sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null sudo apt update
- Install Docker Engine and the Compose plugin
Install Docker Engine, containerd, Buildx, and Docker's current Compose plugin from the configured repository. The supported Linux command form is docker compose; the hyphenated docker-compose standalone path is legacy compatibility guidance.
Changes system state: review before running
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl status docker --no-pager sudo docker compose version sudo docker run --rm hello-world
- Create a small Compose project
Create a dedicated project directory and a Compose file. This example keeps the service intentionally small so that deployment, validation, and rollback behavior are easy to observe.
Changes system state: review before running
mkdir -p ~/my_docker_app cd ~/my_docker_app cat > compose.yaml <<'EOF' services: web: image: nginx:stable-alpine restart: unless-stopped ports: - "8080:80" healthcheck: test: ["CMD", "nginx", "-t"] interval: 30s timeout: 5s retries: 3 EOF sudo docker compose config - Deploy and validate the service
Start the project, confirm Compose reports the service as running, and verify the published endpoint from the host. Do not treat a successful up command alone as proof that the application is healthy.
Changes system state: review before running
cd ~/my_docker_app sudo docker compose up -d sudo docker compose ps curl -fsS http://127.0.0.1:8080/ > /dev/null && echo 'HTTP validation passed'
- Use a controlled update workflow
Record the currently deployed image reference and preserve the known-good Compose file before pulling a replacement. For a production-like rollback requirement, pin tested image digests instead of relying on a floating tag such as stable-alpine.
Changes system state: review before running
cd ~/my_docker_app cp compose.yaml compose.yaml.before-update sudo docker compose images sudo docker compose pull sudo docker compose up -d sudo docker compose ps curl -fsS http://127.0.0.1:8080/ > /dev/null && echo 'Post-update validation passed'
Validation
Confirm Docker Engine and Compose are installed from the expected path.
Read-only command: verify target and scope
sudo docker version sudo docker compose version
Expected result: Both commands return version information without falling back to the legacy `docker-compose` executable.Confirm the service is running and its health check is succeeding.
Read-only command: verify target and scope
cd ~/my_docker_app && sudo docker compose ps curl -fsS http://127.0.0.1:8080/ > /dev/null && echo 'Endpoint healthy'
Expected result: The Compose service remains up and the host receives a successful HTTP response from port 8080.Review recent service logs for restart loops or application errors before declaring the change complete.
Read-only command: verify target and scope
cd ~/my_docker_app && sudo docker compose logs --tail=100 web
Troubleshooting
If Docker does not start, inspect the service state and recent daemon logs before reinstalling packages.
Read-only command: verify target and scope
sudo systemctl status docker --no-pager sudo journalctl -u docker --since '-15 minutes' --no-pager
If the container starts but the endpoint fails, separate container state, health, port publishing, and application logs.
Read-only command: verify target and scope
cd ~/my_docker_app && sudo docker compose ps cd ~/my_docker_app && sudo docker compose logs --tail=100 web sudo ss -lntp | grep ':8080'
If you intentionally configure non-root Docker CLI access, follow Docker's Linux post-install guidance rather than hard-coding a username. Membership in the `docker` group grants root-level privileges.
Cleanup or Rollback
Stop and remove only the containers and network created by this project. This command does not remove named volumes unless you explicitly add `--volumes`.
Changes system state: review before running
cd ~/my_docker_app && sudo docker compose down
If an update fails and the previous Compose definition referenced a known-good immutable tag or digest, restore that definition and redeploy it.
Warning: Restoring the Compose file alone is not a guaranteed image rollback when the file uses a mutable tag. Reliable rollback requires retaining or pinning the known-good image reference.
Changes system state: review before running
cd ~/my_docker_app && cp compose.yaml.before-update compose.yaml cd ~/my_docker_app && sudo docker compose up -d
Next Improvements
Replace the example floating image tag with a tested immutable image digest when reliable rollback is a requirement.
Add named volumes only after documenting which data must persist and how it will be backed up and restored.
Add centralized monitoring or log collection once the single-host deployment is stable.
