Overview

Running migetpacks on self-hosted CI/CD runners (GitHub Actions, GitLab, etc.) gives you control over the build environment and enables persistent caching. However, storage architecture and configuration significantly impact build performance.

Storage Architecture

The underlying storage for /var/lib/docker (where Docker stores layers and images) directly affects build speed.

Local NVMe/SSD

Best performance. Docker layer operations (unpacking, caching, building) are I/O-intensive and benefit from low-latency local storage.

Distributed Storage (CephFS, NFS, EFS)

Slower due to the I/O path. Docker typically uses a loopback device on top of distributed filesystems, adding overhead:
Symptoms of slow distributed storage:
  • High iowait during layer unpacking (visible in top or iostat)
  • First builds taking 5-10x longer than subsequent builds
  • Slow docker pull operations
High iowait during layer unpacking is normal for distributed storage. This is not a migetpacks issue — it is inherent to Docker’s storage driver writing many small files through the network storage path.

Storage Driver Options

migetpacks defaults to overlay2 but supports alternative storage drivers via the STORAGE_DRIVER environment variable:
Use fuse-overlayfs when running nested DinD (e.g., migetpacks inside another Docker container) where overlay2 is not available.

Performance Tips

1. Pre-pull Base Images

Pull commonly used base images during runner initialization to avoid cold-start delays:

2. Use Registry Mirrors

Configure a pull-through cache registry (e.g., Harbor proxy cache) to avoid hitting Docker Hub rate limits and reduce pull times:
The mirror is configured in the Docker daemon’s registry-mirrors setting inside migetpacks, so all image pulls automatically try the mirror first.

3. Persistent Cache Directory

Mount a persistent directory for package manager caches that survives runner restarts:

4. Docker Layer Cache Persistence

Docker layer cache persists in /var/lib/docker between builds on the same runner. This means:
  • Base images are pulled only once
  • Unchanged layers from previous builds are reused
  • BuildKit inline cache provides cross-build layer reuse
Ensure your runner does not prune Docker images between builds unless disk space is constrained.

5. Registry-Based BuildKit Cache

Use CACHE_IMAGE to store and retrieve BuildKit cache from a registry. This works across runners and survives runner reprovisioning:

GitHub Actions Workflow

A complete workflow for self-hosted runners with persistent caching:

Build Time Expectations

These times vary based on application size, dependency count, and network speed to registries.

Troubleshooting

High iowait during builds

This is normal for distributed storage. The Docker storage driver writes many small files during layer unpacking. Consider moving /var/lib/docker to local SSD if available.

Docker Hub rate limits

Use REGISTRY_MIRROR to configure a pull-through cache, or authenticate with Docker Hub to increase rate limits:

Out of disk space

Docker layer cache grows over time. Schedule periodic pruning on runners:

Slow first build after runner restart

The first build after a restart needs to pull base images and warm the layer cache. Use pre-pulling in your runner startup script to minimize this delay.