# Docker

import { Aside } from '@astrojs/starlight/components';

Avrea accelerates Docker builds by serving BuildKit's `type=gha` cache backend
from a colocated cache instead of GitHub's remote blob storage.

## Docker cache on persistent disk

Repositories with persistent disks available can enable **Docker cache on
persistent disk** under repository cache settings. Enable persistent disks for
Linux first. The setting defaults off and takes effect on subsequent jobs that
receive a disk.

This preserves Docker images, local build cache, and Docker volumes on the
repository's persistent disk. Plain `docker build` and the default Docker
builder reuse their cache automatically. For a container-based Buildx builder,
use a stable builder name and retain its state:

```yaml title="workflow.yml"
- uses: docker/setup-buildx-action@v4
  with:
    name: project-build
    keep-state: true
```

Use different stable names for independent builders. Without `keep-state`, the
setup action removes its BuildKit state during cleanup. Remote `type=gha`
caching can still be used alongside disk caching.

The first run starts with an empty Docker store. Cache generations are separated
by guest architecture, OS version, Docker version, and containerd version, so
image upgrades can start a new cache. Unsupported images, unavailable disks,
and insufficient setup time or free space fall back to the image's normal Docker
storage. The initial implementation supports Linux guests using the external
containerd image store and overlayfs snapshotter.

If a cached store cannot start, Avrea restores ordinary Docker and marks that
cache generation unusable. After a successful rollback, other disk content can
still be published, and later jobs skip the failed generation. A runtime image
upgrade can create a fresh generation. Failure to restore Docker safely stops
the VM and prevents publication.

The cache follows the disk's existing branch publication policy. Disposable jobs
can read the published cache but cannot update it. Concurrent writers do not
merge their changes: a stale writer's new cache entries can be discarded.
Containers and temporary Docker networks are removed after job cleanup; images,
build cache, and volumes remain. Docker client login credentials stay on the
ephemeral runner filesystem.

Job changes to `/etc/docker/daemon.json` stay on the ephemeral filesystem. They
do not prevent disk publication after both Docker stores are safely stopped
and unmounted.

<Aside type="caution">
Every job allowed to read the persistent disk can read its cached content,
including private images and files in image layers or volumes. Keep secrets out
of these stores. The current default disk policy also permits disposable fork
jobs to read the published disk.
</Aside>

The default builder receives a garbage collection budget of half the disk size,
capped at 20 GiB, unless the image already configures its own GC policy.
Container-based builders use their own GC settings. This budget is not a quota
on pulled images or Docker volumes; monitor free space and manage those through
Docker. Disabling either setting retains existing disk data.

## On Avrea runners

Use the standard `docker/build-push-action` with the `type=gha` cache backend,
pointing it at the colocated cache via `url_v2`:

```yaml title="workflow.yml"
jobs:
  build:
    runs-on: avrea-ubuntu-latest-2-vcpu
    steps:
      - uses: actions/checkout@v7
      - uses: docker/setup-buildx-action@v4
      - uses: docker/build-push-action@v7
        with:
          context: .
          cache-from: type=gha,url_v2=https://cache.avrea.com/
          cache-to: type=gha,url_v2=https://cache.avrea.com/,mode=max
```

<Aside>
`url_v2=https://cache.avrea.com/` is required. BuildKit's `type=gha` backend
talks to GitHub's cache service by default; `url_v2` redirects it to the Avrea
colocated cache. Without it, builds on Linux amd64 fall back to the slow
upstream GitHub Actions cache, and builds on Linux ARM fail.
</Aside>

On the first run, BuildKit builds all layers and exports them to the cache.
On subsequent runs with unchanged inputs, every `RUN` step hits the cached
layer and the build completes in seconds.

### `mode=max` vs `mode=min`

- **`mode=max`** exports all layers, including intermediate build stages.
  Required for multi-stage builds (e.g. a `build` stage that compiles your
  app and a `scratch`/`alpine` final stage).
- **`mode=min`** (default) exports only the final image layers. The build
  stage is not cached and must re-execute on every run.

Use `mode=max` unless you have a specific reason not to.

### Cache scope

If you have multiple Dockerfiles or matrix builds, use `scope=` to isolate
their caches:

```yaml
cache-from: type=gha,url_v2=https://cache.avrea.com/,scope=backend
cache-to: type=gha,url_v2=https://cache.avrea.com/,mode=max,scope=backend
```

### `.dockerignore`

Add `.git` to your `.dockerignore` — otherwise `COPY . .` invalidates
the layer cache on every run because `.git/` contains files with
different timestamps even when the same commit is checked out.

```text title=".dockerignore"
.git
```