Understanding Images


An image is a metadata file that lists all artifacts needed for a specific CI job. Understanding images is important to maintaining Develocity Artifact Cache and Setup Cache efficiency.

What Is an Image?

An image is a metadata file stored at the Edge that includes:

  • List of build artifacts, like dependencies and/or build initialization inputs

  • OS and architecture information (Linux x64, Windows ARM64, etc.)

  • Build tool content type metadata (Maven, Gradle, npm)

  • Generation number (how many times the job has run)

An image isn’t a container image or archive file. It’s a lightweight metadata file that references artifacts stored separately at the Edge.
Artifacts with a -SNAPSHOT version qualifier are not stored in images, as snapshot versions are mutable and not safe to cache.

How Images Work

Image Lifecycle

  1. First Build: No image exists, Artifact Cache CLI creates a new one

  2. Store: Artifacts are uploaded, image is created

  3. Subsequent Builds: Image is found, artifacts are restored

  4. Growth: New dependencies added over time expand the image

  5. Refresh: Periodic rebuilds create clean images

Image Naming

Every image should have a unique name that identifies the CI job, branch, and environment.

Properties of a Good Image Name

A well-chosen image name should be unique and stable to ensure effective caching.

Unique:

  • Must uniquely identify the specific build context

  • Include the project name, branch, and environment characteristics (OS, architecture)

  • Prevents cache collisions between different builds

Stable:

  • Avoid including dynamic values like timestamps, run numbers, or commit IDs

  • Should remain constant across builds in the same context

Automatic Generation

If you don’t provide an --image-name, the Artifact Cache CLI automatically generates one based on specifics of each supported CI Environment (GitHub Actions, Jenkins).

Manual Naming

You can specify custom image names using the --image-name option:

java -jar develocity-artifact-cache-cli.jar restore \
  --dv-server=https://develocity.example.com \
  --gradle-home=$HOME/.gradle \
  --image-name=my-project-main-build

When to Use Manual Names:

  • Sharing cache across multiple jobs

  • Debugging specific cache issues

  • Non-standard CI platforms

  • Custom caching strategies

Best Practices:

  • Use descriptive names: project-branch-platform

  • Keep names stable (no timestamps or random values)

  • Include relevant context (branch, OS, arch if important)

  • Avoid special characters

  • Keep names short and use only alphanumeric characters and underscores

Image names that contain special characters or exceed the maximum length are automatically transformed into a compact representation. This is normal and doesn’t affect functionality, but the name shown in logs may differ from the name you provided.

Multiple Fallback Images

You can specify multiple image names for fallback scenarios:

--image-name=feature-branch-123 \
--image-name=main-branch

The Artifact Cache CLI tries each image in order. This is useful for feature branches that want to fall back to the main branch cache.

Image Growth and Management

How Images Grow

Every time the store command runs, the image may grow.

New Dependencies Added:

If your project adds a new dependency, it’s added to the image.

Transitive Dependencies:

Dependency version bumps bring in new transitive dependencies.

Build Tool Updates:

Upgrading Gradle or Maven may download new distributions or plugins.

What Happens:

  1. New artifacts are uploaded to Edge node

  2. Image is updated to include new artifact references

  3. Image size grows incrementally

Obsolete Artifacts

When you remove a dependency from your project, the artifact remains in the image.

Impact:

  • Image contains unnecessary artifacts

  • Restore downloads artifacts no longer needed

  • Edge node storage is wasted

  • Restore time increases slightly

Solution:

Smart cleanup (see below) automatically prevents obsolete artifacts from being stored for Gradle and Maven.

For npm, it’s advised to review defaults for and configure image refresh to remove obsolete artifacts preventing image growth.

Smart Cleanup

Smart cleanup automatically mitigates image growth by excluding unused artifacts during the store phase. The Artifact Cache CLI integrates with the build tool to identify which artifacts were actually used during the build. Only used artifacts are included in the stored image; unused artifacts already part of the image are filtered out.

This keeps the image lean after every store, without requiring a full image refresh (as covered in the Image Refresh section).

Smart cleanup is supported for Gradle and Maven builds. Maven requires Develocity Maven Extension version 2.4.0 or later.

Some types of Gradle cache are only removed when you upgrade Gradle or when that type of cacheable content grows too large.

Image Refresh

What Is a Refresh?

A refresh rebuilds the image from scratch based on the current build’s actual artifact usage.

During Refresh:

  1. Artifact Cache CLI tracks which artifacts are actually used during the build

  2. Old image is discarded

  3. New image is created with only used artifacts

  4. Unused artifacts remain on Edge but aren’t referenced

After Refresh:

  • Image contains only currently needed artifacts

  • Restore is faster (fewer artifacts)

  • Edge storage can reclaim space via normal eviction

When to Refresh

Refresh is controlled by the restore command. The following options are only available on restore, not on store.

Automatically:

Use --refresh-every-period and/or --refresh-every-generation:

--refresh-every-period=P1M  # Refresh every month (default)
--refresh-every-generation=50  # Refresh every 50 builds

Combine both - whichever condition is met first triggers the refresh:

--refresh-every-period=P1M \
--refresh-every-generation=50

Force an immediate refresh at a scheduled interval (for example, a weekly triggered CI job):

--refresh-now

Generation Counter

Each time a CI job runs (with a successful store), the image’s generation number increments.

Image Storage and Lifecycle

Where Images Are Stored

Images are stored at the Edge, not on the CI agent.

During Restore:

  • Artifact Cache CLI downloads image from the Edge

  • Uses image to determine which artifacts to download

During Store:

  • Artifact Cache CLI uploads new artifacts to Edge

  • Updates image on Edge

CI Agent:

  • No persistent image storage

  • Only temporary working files in .develocity/

Image Eviction

Edge nodes recognize images as high-priority assets and apply a smart eviction policy that prevents frequent image regeneration. After a certain point, infrequently used images become subject to the Edge node’s cache storage eviction policy (least-recently used).

Impact of Eviction:

  • Next job creates a new image (cold cache)

  • One-time slowdown, then back to normal

Prevention:

  • Smart eviction policy is sufficient for most use cases

  • Adequately size Edge node storage

  • Run jobs frequently enough to keep images "hot"

Image Isolation

By Environment

Images should be separate for different:

  • Operating Systems: Linux vs. Windows vs. macOS

  • Architectures: x64 vs. ARM64

  • Branches: main vs. feature/xyz

  • Jobs: Different workflow jobs have different images

This should be taken into consideration when naming images manually. Automatic image name generation already takes care of this on the supported CI Environments.

Sharing Images

Same Image Reused When:

  • Same CI job runs multiple times

  • Branch and platform match

  • Image name matches (if manual naming)

Different Images When:

  • Different branches

  • Different OS/architecture

  • Different workflow/job names

Best Practices

Let Automatic Generation Handle Names

For standard GitHub Actions and Jenkins workflows, automatic generation is recommended.

Do:

java -jar artifact-cache-cli.jar restore \
  --dv-server=$DV_SERVER \
  --gradle-home=$HOME/.gradle

Avoid (unless needed):

--image-name=build-${{ github.run_number }}  # Bad: changes per build

Use Time-Based Refresh

Set a reasonable refresh period:

--refresh-every-period=P1M  # Monthly is a good default

Consider Feature Branch Strategy

Use fallback images for feature branches:

--image-name=feature-xyz \
--image-name=main

This allows feature branches to leverage the main branch cache initially.