# 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: 1. Uses the `figlet-rs` crate to display ASCII art text (banner-style) 2. Uses the `neofetch` crate 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 ```bash # 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.lock` if 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`: ```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: 1. Using dedicated runners for DooD jobs 2. Implementing runner token rotation 3. Restricting which projects can use DooD runners 4. Using rootless Docker on the host 5. Implementing image scanning and security policies ## Advantages of This Setup 1. **Reproducibility**: Nix ensures exact dependency versions 2. **Efficiency**: DooD avoids DinD overhead and shares layer cache 3. **Minimal Images**: Rust crates eliminate system binary dependencies; Nix's `buildLayeredImage` creates optimized layers 4. **Type Safety**: Rust provides compile-time guarantees 5. **Declarative**: Everything is defined in code 6. **Portability**: Pure Rust implementation works across platforms without requiring system tools ## Environment Variables The GitLab CI uses these key variables for DooD: ```yaml 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.sock` mounted - 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 `docker` group on the host, or - Change socket permissions (less secure) ### Nix build fails - Ensure experimental features are enabled in `nix.conf` - Check that `Cargo.lock` is 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: ```yaml 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