feat: add docker-build-nix action for reproducible OCI images
Add reusable action for building Docker images with Nix flakes: - Full reproducibility with Nix derivations - Attic cache integration for build artifacts - Optimized layering with dockerTools.buildLayeredImage - Automatic Nix binary cache usage Use this instead of docker-build when you want: - Bit-for-bit identical builds - Better caching via Attic/Nix - Smaller, optimized images Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ac56aac0a7
commit
b163ffa64b
2 changed files with 231 additions and 0 deletions
143
docker-build-nix/README.md
Normal file
143
docker-build-nix/README.md
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# docker-build-nix
|
||||
|
||||
Build and push Docker/OCI images generated by Nix flakes, with Attic cache integration.
|
||||
|
||||
## Features
|
||||
|
||||
- **Reproducible builds**: Uses Nix flakes for bit-for-bit identical images
|
||||
- **Attic caching**: Pushes build artifacts to your Attic cache for faster subsequent builds
|
||||
- **Nix store optimization**: Leverages Nix's existing binary caches (nixos.org + your Attic)
|
||||
- **Smaller images**: Nix `dockerTools.buildLayeredImage` creates optimized layers
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic example
|
||||
|
||||
```yaml
|
||||
name: Build Docker Image
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: nix # Requires runner with Nix
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: https://git.toph.so/toph/ci-actions/docker-build-nix@main
|
||||
with:
|
||||
flake-output: .#dojo-image
|
||||
image-name: toph/dojo
|
||||
registry-password: ${{ secrets.GITEA_TOKEN }}
|
||||
env:
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
```
|
||||
|
||||
### Multiple images
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
build-web:
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://git.toph.so/toph/ci-actions/docker-build-nix@main
|
||||
with:
|
||||
flake-output: .#dojo-image
|
||||
image-name: toph/dojo
|
||||
image-tag: main
|
||||
registry-password: ${{ secrets.GITEA_TOKEN }}
|
||||
env:
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
|
||||
build-agent:
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://git.toph.so/toph/ci-actions/docker-build-nix@main
|
||||
with:
|
||||
flake-output: .#agent-image
|
||||
image-name: toph/dojo/agent
|
||||
image-tag: main
|
||||
registry-password: ${{ secrets.GITEA_TOKEN }}
|
||||
env:
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `flake-output` | ✅ | - | Nix flake output (e.g., `.#dojo-image`) |
|
||||
| `image-name` | ✅ | - | Target image name (e.g., `user/repo`) |
|
||||
| `registry-password` | ✅ | - | Registry password/token |
|
||||
| `image-tag` | ❌ | `main` | Image tag |
|
||||
| `registry` | ❌ | `git.toph.so` | Docker registry |
|
||||
| `registry-username` | ❌ | `${{ gitea.actor }}` | Registry username |
|
||||
| `cache-name` | ❌ | `ci` | Attic cache name |
|
||||
| `attic-endpoint` | ❌ | `https://cache.toph.so` | Attic endpoint |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `ATTIC_TOKEN` | Optional | Attic token for pushing to cache. If not set, skips cache push. |
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Build with Nix**: Runs `nix build <flake-output>` which uses Nix caching
|
||||
2. **Push to Attic**: Uploads the entire build closure to your Attic cache
|
||||
3. **Load to Docker**: Loads the OCI tarball into local Docker daemon
|
||||
4. **Tag & Push**: Tags and pushes to your Docker registry
|
||||
|
||||
## Example Flake
|
||||
|
||||
Your `flake.nix` should export image outputs:
|
||||
|
||||
```nix
|
||||
{
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
in
|
||||
{
|
||||
packages.x86_64-linux = {
|
||||
# Your app
|
||||
default = pkgs.callPackage ./package.nix { };
|
||||
|
||||
# OCI image
|
||||
dojo-image = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "dojo";
|
||||
tag = "latest";
|
||||
contents = [ self.packages.x86_64-linux.default ];
|
||||
config = {
|
||||
Cmd = [ "${self.packages.x86_64-linux.default}/bin/dojo" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Nix runner**: Runner with `nix` label using `docker://registry.toph.so/nix-runner:latest`
|
||||
- **Attic cache**: Optional but recommended for caching (`cache.toph.so`)
|
||||
- **ATTIC_TOKEN**: Set in Forgejo secrets if you want cache push
|
||||
- **GITEA_TOKEN**: Auto-available in Forgejo Actions
|
||||
|
||||
## Comparison with docker-build
|
||||
|
||||
| Feature | docker-build | docker-build-nix |
|
||||
|---------|-------------|------------------|
|
||||
| Build method | Docker | Nix |
|
||||
| Reproducibility | Layer-dependent | Byte-for-byte |
|
||||
| Caching | S3 layers | Attic derivations |
|
||||
| Image size | Larger | Optimized |
|
||||
| Runner | Any | Requires Nix |
|
||||
|
||||
## See Also
|
||||
|
||||
- [docker-build](../docker-build/) - Traditional Docker builds with S3 cache
|
||||
- [push-nix-cache](../push-nix-cache/) - Push arbitrary Nix paths to Attic
|
||||
88
docker-build-nix/action.yaml
Normal file
88
docker-build-nix/action.yaml
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
name: Build and Push Docker Image from Nix
|
||||
description: Build OCI image with Nix flake, push to registry with Attic caching
|
||||
|
||||
inputs:
|
||||
flake-output:
|
||||
description: 'Nix flake output for the OCI image (e.g., .#dojo-image)'
|
||||
required: true
|
||||
|
||||
image-name:
|
||||
description: 'Target image name in registry (e.g., git.toph.so/user/repo)'
|
||||
required: true
|
||||
|
||||
image-tag:
|
||||
description: 'Image tag'
|
||||
required: false
|
||||
default: 'main'
|
||||
|
||||
registry:
|
||||
description: 'Docker registry'
|
||||
required: false
|
||||
default: 'git.toph.so'
|
||||
|
||||
registry-username:
|
||||
description: 'Registry username'
|
||||
required: false
|
||||
default: ${{ gitea.actor }}
|
||||
|
||||
registry-password:
|
||||
description: 'Registry password/token'
|
||||
required: true
|
||||
|
||||
cache-name:
|
||||
description: 'Attic cache name to push build artifacts'
|
||||
required: false
|
||||
default: 'ci'
|
||||
|
||||
attic-endpoint:
|
||||
description: 'Attic cache endpoint'
|
||||
required: false
|
||||
default: 'https://cache.toph.so'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build OCI image with Nix
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Building ${{ inputs.flake-output }}..."
|
||||
nix build "${{ inputs.flake-output }}" --print-build-logs
|
||||
|
||||
- name: Push build artifacts to Attic cache
|
||||
shell: bash
|
||||
if: env.ATTIC_TOKEN != ''
|
||||
env:
|
||||
ATTIC_TOKEN: ${{ env.ATTIC_TOKEN }}
|
||||
run: |
|
||||
# Configure attic client
|
||||
mkdir -p ~/.config/attic
|
||||
cat > ~/.config/attic/config.toml <<EOF
|
||||
[servers.cache]
|
||||
endpoint = "${{ inputs.attic-endpoint }}"
|
||||
token = "${ATTIC_TOKEN}"
|
||||
EOF
|
||||
|
||||
# Push entire closure to cache
|
||||
attic push "${{ inputs.cache-name }}" ./result
|
||||
|
||||
- name: Load image into Docker
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Loading OCI image into Docker..."
|
||||
docker load < ./result
|
||||
|
||||
- name: Tag and push to registry
|
||||
shell: bash
|
||||
run: |
|
||||
# Extract image name from the loaded output
|
||||
IMAGE_ID=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n1)
|
||||
echo "Loaded image: $IMAGE_ID"
|
||||
|
||||
# Tag with target name
|
||||
TARGET_IMAGE="${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.image-tag }}"
|
||||
echo "Tagging as: $TARGET_IMAGE"
|
||||
docker tag "$IMAGE_ID" "$TARGET_IMAGE"
|
||||
|
||||
# Login and push
|
||||
echo "${{ inputs.registry-password }}" | docker login ${{ inputs.registry }} -u ${{ inputs.registry-username }} --password-stdin
|
||||
docker push "$TARGET_IMAGE"
|
||||
Loading…
Add table
Reference in a new issue