| .devcontainer | ||
| src | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| Dockerfile | ||
| FIGLET_FONTS.md | ||
| flake.nix | ||
| nix.dock | ||
| README.md | ||
Hello World - Docker-outside-of-Docker with Rust, Nix, and GitLab CI
This project demonstrates the Docker-outside-of-Docker (DooD) pattern using:
- Rust for the application
- Nix for reproducible dependency management
- GitLab CI for automated builds
What is Docker-outside-of-Docker (DooD)?
Docker-outside-of-Docker is a pattern where CI/CD pipelines build Docker images by mounting the host's Docker socket (/var/run/docker.sock) into the CI container, rather than running Docker-in-Docker (DinD). This approach:
- ✅ Avoids nested virtualization overhead
- ✅ Shares the Docker layer cache with the host
- ✅ Simplifies networking
- ⚠️ Requires proper security considerations (the container has full Docker daemon access)
Project Structure
.
├── src/
│ └── main.rs # Rust application that calls neofetch and banner
├── Cargo.toml # Rust package manifest
├── flake.nix # Nix flake for pinned dependencies
├── .gitlab-ci.yml # GitLab CI pipeline
└── README.md # This file
The Application
The Rust application (src/main.rs) is a simple hello-world program that:
- Uses the
figlet-rscrate to display ASCII art text (banner-style) - Uses the
neofetchcrate to gather and display system information
All functionality is implemented using native Rust crates, requiring no system binaries.
Nix Flake
The flake.nix file:
- Pins exact versions of all dependencies (Rust toolchain, crates dependencies via Cargo.lock)
- Defines how to build the Rust application
- Creates a minimal Docker image with only the application binary and essential runtime dependencies
- Provides a development shell for local development
Since we're using Rust crates (neofetch and figlet-rs) instead of system binaries, the Docker image is much smaller and more portable.
Building locally with Nix
# Enter the development environment
nix develop
# Build the application
nix build .#app
# Run the application
nix run .#app
# Build the Docker image
nix build .#docker
# Load the Docker image
docker load < result
docker run hello-world:latest
GitLab CI Pipeline
The .gitlab-ci.yml implements DooD with three stages:
1. Build Stage
- Uses a Nix container to build the Rust application
- Generates
Cargo.lockif needed - Produces build artifacts
2. Test Stage
- Runs tests in a Nix environment
- Verifies the application works
3. Package Stage (Two Approaches)
Approach A: build-docker-image (Recommended)
- Builds the Docker image using Nix's
dockerTools.buildLayeredImage - Loads the image into the host's Docker daemon via DooD
- Tests the container
- Ready for pushing to a registry
Approach B: build-docker-traditional (Alternative)
- Uses a traditional Dockerfile
- Builds in two stages: Nix build + runtime image
- Manual trigger only
GitLab Runner Configuration
For DooD to work, your GitLab Runner must be configured with Docker socket access:
Using Docker Executor
In /etc/gitlab-runner/config.toml:
[[runners]]
name = "docker-runner"
executor = "docker"
[runners.docker]
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
privileged = false # DooD doesn't require privileged mode
Security Considerations
⚠️ Important: Mounting the Docker socket gives the container full control over the Docker daemon. Only use this on trusted runners. Consider:
- Using dedicated runners for DooD jobs
- Implementing runner token rotation
- Restricting which projects can use DooD runners
- Using rootless Docker on the host
- Implementing image scanning and security policies
Advantages of This Setup
- Reproducibility: Nix ensures exact dependency versions
- Efficiency: DooD avoids DinD overhead and shares layer cache
- Minimal Images: Rust crates eliminate system binary dependencies; Nix's
buildLayeredImagecreates optimized layers - Type Safety: Rust provides compile-time guarantees
- Declarative: Everything is defined in code
- Portability: Pure Rust implementation works across platforms without requiring system tools
Environment Variables
The GitLab CI uses these key variables for DooD:
DOCKER_HOST: unix:///var/run/docker.sock # Use host's Docker daemon
DOCKER_TLS_CERTDIR: "" # Disable TLS for local socket
Troubleshooting
"Cannot connect to Docker daemon"
- Ensure the runner has
/var/run/docker.sockmounted - Check socket permissions on the host
- Verify the Docker daemon is running on the host
"Permission denied" on Docker socket
- The container user needs access to the socket
- Add the container user to the
dockergroup on the host, or - Change socket permissions (less secure)
Nix build fails
- Ensure experimental features are enabled in
nix.conf - Check that
Cargo.lockis generated before building - Verify network access for downloading dependencies
Alternative: Docker-in-Docker
If you prefer Docker-in-Docker instead of DooD, modify the CI config:
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
Note: DinD requires privileged: true in the runner config and has performance implications.
License
MIT