DEV Community

Cover image for Free, self-hosted, instant Nx remote cache with Docker and Cacheiro Instants. Ready for S3, GCS, Azure and disk
Renato Rodrigues
Renato Rodrigues

Posted on

Free, self-hosted, instant Nx remote cache with Docker and Cacheiro Instants. Ready for S3, GCS, Azure and disk

If you maintain a monorepo with Nx or Lerna, you know how much remote caching helps: whatever a developer or a CI job has already built doesn't need to be built again by anyone else. The problem is where to host that cache without paying for a managed service and without building a server from scratch.

That's where Cacheiro comes in: a free, open source remote cache server for Nx and Lerna. And with the new Cacheiro Instants feature, spinning up one of these servers is now a single docker run.

In this post I'll cover where Cacheiro Instants came from, what it delivers and how to use each of the four variants.

What is Cacheiro

Cacheiro is a self-hosted, open source (MIT) server that implements the Nx remote cache OpenAPI specification. It works with Nx and Lerna (which relies on Nx for task orchestration), it's modular, and it has four storage backends: filesystem, Amazon S3, Google Cloud Storage and Azure Blob Storage.

Why does it exist? Nx deprecated its official self-hosted cache packages after CVE-2025-36852, a critical cache poisoning vulnerability, and left two alternatives: its paid service or building your own server. Cacheiro is my answer to the second one, built to be reused by anyone. The full story is in the launch article.

To make your workspace use the server, you only need to set two environment variables (the token is optional):

export NX_SELF_HOSTED_REMOTE_CACHE_SERVER="https://mycache.server"
export NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN="your-secure-token-here"
Enter fullscreen mode Exit fullscreen mode

And run your usual commands:

nx run-many -t build
# or
lerna run build
Enter fullscreen mode Exit fullscreen mode

The motivation: from library to ready-made image

Originally, Cacheiro was distributed only as a library. It could be embedded into existing systems and customized freely. That philosophy gives developers freedom, but it creates extra work for people who just want to use the cache server, especially DevOps teams.

The first step: Cacheiro Runner

To reduce that friction, the project already offered Cacheiro Runner, a ready-to-use example application that runs Cacheiro. It loads the configuration, validates it, creates the store and starts the server. But Cacheiro Runner is an opinionated implementation, meant to serve as a reference: to use S3, GCS or Azure, for example, you have to edit the source code and adjust the Dockerfile.

The solution: Cacheiro Instants ⚡

Cacheiro Instants is exactly the missing step: 100% ready-to-use Docker images, in four flavors (fs, s3, gcs and azure). You run the image in your CI or on your infrastructure, pass a parameter or two, and that's it.

No build, no code configuration, no custom deploy.

The images live on the GitHub Container Registry (GHCR):

Flavor Image
Filesystem ghcr.io/rerodrigues/cacheiro-instants-fs
Amazon S3 ghcr.io/rerodrigues/cacheiro-instants-s3
Google Cloud Storage ghcr.io/rerodrigues/cacheiro-instants-gcs
Azure Blob Storage ghcr.io/rerodrigues/cacheiro-instants-azure

Each image includes only the store package and the cloud SDK for its own flavor. The fs image, for example, carries no AWS, Google or Azure SDK.

The full documentation for every environment variable is on the Cacheiro Instants page of the project site.

Getting it running

All flavors share the same base configuration: port 3000 and host 0.0.0.0 are already set in the image, and authentication is done with a bearer token in CACHEIRO_AUTH_TOKEN. If you don't set the token, authentication is disabled, so always set one in exposed environments.

Filesystem (fs)

The simplest option to get started, or for a single server. Data lives in /cache, which you mount as a volume:

docker run -p 3000:3000 \
  -e CACHEIRO_AUTH_TOKEN=my-secret-token \
  -v $(pwd)/cache:/cache \
  ghcr.io/rerodrigues/cacheiro-instants-fs
Enter fullscreen mode Exit fullscreen mode

In this flavor, artifacts expire after 7 days by default (CACHEIRO_CACHE_TTL_DAYS, and 0 disables expiration).

Amazon S3 (s3)

It works with S3 and also with compatible alternatives such as MinIO, LocalStack, Cloudflare R2 and DigitalOcean Spaces (via S3_ENDPOINT):

docker run -p 3000:3000 \
  -e CACHEIRO_AUTH_TOKEN=my-secret-token \
  -e S3_BUCKET=my-nx-cache \
  -e S3_REGION=us-east-1 \
  -e AWS_ACCESS_KEY_ID=... \
  -e AWS_SECRET_ACCESS_KEY=... \
  ghcr.io/rerodrigues/cacheiro-instants-s3
Enter fullscreen mode Exit fullscreen mode

Google Cloud Storage (gcs)

Credentials follow Google's standard mechanisms (service account file or workload identity):

docker run -p 3000:3000 \
  -e CACHEIRO_AUTH_TOKEN=my-secret-token \
  -e GCS_BUCKET=my-nx-cache \
  -v $(pwd)/gcp-credentials.json:/creds.json:ro \
  -e GOOGLE_APPLICATION_CREDENTIALS=/creds.json \
  ghcr.io/rerodrigues/cacheiro-instants-gcs
Enter fullscreen mode Exit fullscreen mode

Azure Blob Storage (azure)

docker run -p 3000:3000 \
  -e CACHEIRO_AUTH_TOKEN=my-secret-token \
  -e AZURE_CONTAINER=my-nx-cache \
  -e AZURE_STORAGE_CONNECTION_STRING="..." \
  ghcr.io/rerodrigues/cacheiro-instants-azure
Enter fullscreen mode Exit fullscreen mode

Instead of the connection string, you can also use AZURE_ACCOUNT_NAME.

Features worth highlighting

Read-only token. With CACHEIRO_AUTH_READ_ONLY_TOKEN you create a second token that only allows reads (GET). A common pattern is to give the full-access token only to the main branch CI, and the read-only one to developer machines and PRs. There's a post on the project site that goes deeper into this fine-grained token control.

Built-in healthcheck. The images ship with a Docker HEALTHCHECK that queries the server's /health endpoint. It follows the port and TLS settings you configured, and it doesn't install anything extra in the image. The result shows up right in docker ps, and orchestrators like Docker Compose can use it to know when the cache is ready:

docker ps
# STATUS: Up 2 minutes (healthy)
Enter fullscreen mode Exit fullscreen mode

Clear configuration errors. All configuration comes from environment variables and is validated before the server starts. If something required is missing or invalid, the container fails right away and lists all the problems at once, instead of making you find them one by one.

Native HTTPS. To enable TLS in any flavor, point to the certificate and key and mount them as a read-only volume:

docker run -p 3000:3000 \
  -e CACHEIRO_AUTH_TOKEN=my-secret-token \
  -e CACHEIRO_TLS_CERT_FILE=/certs/cert.pem \
  -e CACHEIRO_TLS_KEY_FILE=/certs/key.pem \
  -v $(pwd)/certs:/certs:ro \
  ghcr.io/rerodrigues/cacheiro-instants-fs
Enter fullscreen mode Exit fullscreen mode

Always up to date, predictably versioned

This was one of my priorities. Whenever any package in the Cacheiro ecosystem gets a new version, the corresponding images are updated too.

There are two kinds of tags:

  • Version tag (for example, 0.4.0): follows the version of the @renatorodrigues/cacheiro-instants package and never changes once published. It's the one to use in production, because it guarantees the same image today and six months from now.
  • latest: points to the newest build of each flavor. Great for trying things out, but it can change without notice.

To pin the version in production:

docker pull ghcr.io/rerodrigues/cacheiro-instants-s3:0.4.0
Enter fullscreen mode Exit fullscreen mode

Two more details:

  • Each flavor is published independently, so only the flavor whose store changed gets a new latest.
  • The images are multi-architecture (amd64 and arm64), which helps people developing on Apple Silicon Macs and running on ARM instances.

Runner or Instants: which one should you pick?

My recommendation:

Testing locally before going to the cloud

The repository includes local emulators for S3, GCS and Azure Blob, so you can test each store without creating any real cloud resources. The instructions are in the emulators section of the README.

Conclusion

Remote caching shouldn't require an infrastructure project. With Cacheiro Instants, the path to a shared CI cache has three steps: pick a flavor, set a few environment variables and run a container.

If you use Nx or Lerna, give it a try. Pick the flavor that fits your infrastructure, start the container and run your build twice: the second run is the one that shows the value.

If the project helped you, leave a star on the repository, open an issue with suggestions or bugs, or tell me in the comments which flavor you'd use and why. Feedback is what moves Cacheiro forward the most.

Links

Top comments (0)