From ded186295d6eeaab6801742806545f0e0681cd56 Mon Sep 17 00:00:00 2001
From: Dylan Ross
Date: Fri, 13 Feb 2026 17:42:04 -0600
Subject: [PATCH 1/3] feat(ci): added actions for ci and release
---
.claude/skills/development-workflow/SKILL.md | 49 ++
.github/workflows/ci.yml | 48 ++
.github/workflows/release.yml | 41 ++
.gitignore | 1 +
.goreleaser.yml | 69 +++
LICENSE.md | 21 +
README.md | 598 +++++--------------
cmd/barnacle/main.go | 7 +
cmd/barnacle/serve.go | 6 +-
9 files changed, 391 insertions(+), 449 deletions(-)
create mode 100644 .claude/skills/development-workflow/SKILL.md
create mode 100644 .github/workflows/ci.yml
create mode 100644 .github/workflows/release.yml
create mode 100644 .goreleaser.yml
create mode 100644 LICENSE.md
diff --git a/.claude/skills/development-workflow/SKILL.md b/.claude/skills/development-workflow/SKILL.md
new file mode 100644
index 0000000..3d34e25
--- /dev/null
+++ b/.claude/skills/development-workflow/SKILL.md
@@ -0,0 +1,49 @@
+---
+name: development-workflow
+description: Branch naming conventions, PR workflow, and development process for this repository
+---
+
+## Branch Naming Conventions
+
+When starting work, create a branch from `main` using the appropriate prefix:
+
+* `feat/` - For new features (e.g., `feat/blob-replication`)
+* `bug/` - For bug fixes (e.g., `bug/redis-connection-timeout`)
+* `doc/` - For documentation changes (e.g., `doc/api-examples`)
+
+## Development Workflow
+
+1. **Create branch** from `main` with the appropriate prefix
+2. **Do the work** - implement the feature, fix, or documentation
+3. **Commit and push** - commit changes with descriptive messages, push to remote
+4. **Create PR** - open a pull request targeting `main`
+5. **Wait for CI** - ensure all pipelines pass
+6. **Get review** - wait for code review approval
+7. **Squash merge** - merge to `main` using squash merge
+
+## Git Commands Reference
+
+```bash
+# Start a new feature
+git checkout main
+git pull origin main
+git checkout -b feat/
+
+# After work is complete
+git add
+git commit -m "feat: description of changes"
+git push -u origin feat/
+
+# Create PR via GitHub CLI
+gh pr create --base main --title "feat: description" --body "..."
+```
+
+## Commit Message Style
+
+Follow conventional commits where appropriate:
+* `feat:` - New feature
+* `fix:` - Bug fix
+* `doc:` or `docs:` - Documentation
+* `refactor:` - Code refactoring
+* `test:` - Adding or updating tests
+* `chore:` - Maintenance tasks
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..af9efe9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,48 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: "1.25"
+
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v6
+ with:
+ version: latest
+
+ test:
+ name: Test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: "1.25"
+
+ - name: Run unit tests
+ run: go test -v $(go list ./... | grep -v /test/e2e) -race
+
+ build:
+ name: Docker Build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Build Docker image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ push: false
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..0e6cea4
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,41 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: "1.25"
+
+ - uses: ko-build/setup-ko@v0.9
+
+ - name: Login to GHCR
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Run GoReleaser
+ uses: goreleaser/goreleaser-action@v6
+ with:
+ version: latest
+ args: release --clean
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ KO_DOCKER_REPO: ghcr.io/pdylanross/barnacle
diff --git a/.gitignore b/.gitignore
index ed20272..c0f6246 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
*.dll
*.so
*.dylib
+dist/
# Test binary, built with `go test -c`
*.test
diff --git a/.goreleaser.yml b/.goreleaser.yml
new file mode 100644
index 0000000..71a981f
--- /dev/null
+++ b/.goreleaser.yml
@@ -0,0 +1,69 @@
+# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
+
+version: 2
+
+project_name: barnacle
+
+builds:
+ - id: barnacle
+ main: ./cmd/barnacle
+ binary: barnacle
+ env:
+ - CGO_ENABLED=0
+ goos:
+ - linux
+ - darwin
+ goarch:
+ - amd64
+ - arm64
+
+kos:
+ - id: barnacle
+ main: ./cmd/barnacle
+ base_image: gcr.io/distroless/static-debian12:nonroot
+ repositories:
+ - ghcr.io/pdylanross/barnacle
+ platforms:
+ - linux/amd64
+ - linux/arm64
+ tags:
+ - "{{.Tag}}"
+ - "{{.Major}}.{{.Minor}}"
+ - "{{.Major}}"
+ - latest
+ labels:
+ org.opencontainers.image.title: barnacle
+ org.opencontainers.image.description: Distributed tiered OCI registry caching proxy
+ org.opencontainers.image.source: https://github.com/pdylanross/barnacle
+ org.opencontainers.image.version: "{{.Version}}"
+ org.opencontainers.image.revision: "{{.Commit}}"
+ org.opencontainers.image.licenses: Apache-2.0
+ creation_time: "{{.CommitTimestamp}}"
+ ko_data_creation_time: "{{.CommitTimestamp}}"
+ sbom: none
+
+archives:
+ - id: default
+ formats:
+ - tar.gz
+ name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
+
+checksum:
+ name_template: "checksums.txt"
+
+changelog:
+ use: github
+ sort: asc
+ filters:
+ exclude:
+ - "^docs:"
+ - "^test:"
+ - "^chore:"
+
+release:
+ github:
+ owner: pdylanross
+ name: barnacle
+ draft: false
+ prerelease: auto
+ name_template: "{{ .Tag }}"
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..77cc6f1
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016-2026 Carlos Alexandro Becker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 7353c50..16d35fd 100644
--- a/README.md
+++ b/README.md
@@ -1,463 +1,165 @@
-# Barnacle
-
-A distributed, tiered OCI registry caching proxy designed for terabyte-scale container images.
-
-## Overview
-
-Barnacle is a Kubernetes-native caching proxy for OCI registries that solves the challenges of managing extremely large container images (multi-terabyte blobs) across distributed infrastructure. Like its namesake that attaches to larger vessels, Barnacle attaches to upstream registries and intelligently caches their content across a distributed fleet of storage pods.
-
-### Why Barnacle?
-
-Modern container workloads increasingly include massive images containing ML models, scientific datasets, and other large artifacts. Traditional registry caching solutions face several challenges:
-
-- **Fixed placement**: Consistent hashing locks blobs to specific nodes, preventing optimization
-- **Inefficient storage**: All blobs treated equally regardless of access patterns, wasting expensive fast storage
-- **Poor scalability**: Large blobs overwhelm memory-based caching systems
-- **Limited capacity**: Cannot efficiently handle terabyte-scale individual blobs
-
-Barnacle addresses these challenges through intelligent metadata-driven placement, automatic storage tiering, and streaming-first architecture.
-
-## Key Features
-
-- **Terabyte-Scale Blob Support**: Stream-based architecture handles blobs of any size without memory constraints
-- **Metadata-Driven Sharding**: Intelligent placement based on capacity, load, and access patterns rather than static hashing
-- **Automatic Storage Tiering**: Cost optimization through hot/warm/cold/archive tiers based on access patterns
-- **Horizontal Scalability**: Add capacity by adding pods; no complex resharding required
-- **Multiple Upstream Registries**: Proxy to Docker Hub, GCR, ECR, Harbor, and private registries simultaneously
-- **Flexible Authentication**: Basic auth, bearer token, and anonymous authentication for upstream registries
-- **Thundering Herd Prevention**: Distributed locking ensures single upstream fetch per blob across the cluster
-- **Read-Only Safe**: Production-ready read-only mode with architecture supporting write operations
-
-## Architecture
-
-### High-Level Design
-
-```
-┌─────────────────────────────────────────────────────────┐
-│ Client Requests │
-│ (docker pull, containerd) │
-└────────────────────┬────────────────────────────────────┘
- │
- ▼
- ┌───────────────────────┐
- │ Load Balancer/ │
- │ Ingress │
- └───────────┬───────────┘
- │
- ┌────────────┴────────────┐
- ▼ ▼
-┌───────────────┐ ┌───────────────┐
-│ Cache Pod 0 │ ◄────► │ Cache Pod N │
-│ (Router) │ │ (Router) │
-└───────┬───────┘ └───────┬───────┘
- │ │
- ▼ ▼
-┌───────────────┐ ┌───────────────┐
-│ Storage Tier │ │ Storage Tier │
-│ (NVMe/SSD/ │ │ (NVMe/SSD/ │
-│ HDD/S3) │ │ HDD/S3) │
-└───────────────┘ └───────────────┘
- │ │
- └────────────┬────────────┘
- ▼
- ┌─────────────┐
- │ Redis │
- │ (Metadata) │
- └─────────────┘
-```
-
-### Core Components
-
-#### 1. Router Component
-Each cache pod runs a router that:
-- Determines blob ownership via metadata lookup (not hashing)
-- Issues 307 redirects to the appropriate storage pod
-- Handles cache misses by fetching from upstream
-- Manages distributed locks during fetch operations
-
-#### 2. Redis Metadata Store
-Redis serves as the source of truth for:
-- **Blob locations**: Maps digests to storage pods and tiers
-- **Node capacity**: Tracks available space, IOPS, and load per pod
-- **Distributed locks**: Prevents duplicate fetches (thundering herd protection)
-- **Access patterns**: Records access frequency for tier migration decisions
-- **Membership**: Tracks active pods for placement decisions
-
-**Why Redis over other options?**
-- Fast metadata lookups (critical path for all requests)
-- Built-in distributed locking with TTL (via Redlock/Redsync)
-- Sorted sets for efficient access pattern tracking
-- Pub/Sub for membership changes
-- AOF persistence provides durability without Raft overhead
-- Simple operations compared to etcd or Consul
-- Single dependency for both metadata and coordination
-
-#### 3. Metadata-Based Sharding
-
-**Why metadata-based instead of consistent hashing?**
-
-Consistent hashing (`digest → hash → pod`) is simple but inflexible:
-- Cannot move blobs after initial placement
-- No consideration of pod capacity or load
-- Wastes space (some pods fill while others sit empty)
-- Makes storage tiering impossible
-
-Metadata-based sharding (`digest → Redis lookup → pod(s)`) provides:
-- Dynamic placement based on current capacity
-- Load-aware distribution
-- Support for storage tiering (move blobs between storage classes)
-- Graceful rebalancing without rehashing
-- Multi-replica support with configurable redundancy
-
-**Placement Algorithm:**
-
-*Current placement (v0.1.0):*
-```
-For new blob:
-1. Query node capacities from Redis
-2. Iterate through storage tiers in order (hot → warm → cold)
-3. Prefer local node if it has sufficient space in current tier
-4. Fall back to first remote node with capacity in current tier
-5. Store metadata mapping digest → {node, tier}
-```
-
-The current algorithm prioritizes storage tiers in order, placing blobs on the fastest
-available tier with sufficient capacity. Local node preference reduces network hops
-for subsequent reads.
-
-*Planned placement (v0.4.0+):*
-```
-For new blob:
-1. Query all pod capacities from Redis
-2. Filter pods by available space
-3. Score by: available_space (50%) + load (30%) + blob_count (20%)
-4. Select N pods with best scores (N = replication factor)
-5. Store metadata mapping digest → [pod1, pod2, ...]
-```
-
-#### 4. Storage Tiering
-
-Blobs automatically migrate between storage tiers based on access patterns:
-
-| Tier | Storage | Cost/GB | Use Case | Demotion After |
-|---------|-------------|---------|----------------------------|----------------|
-| Hot | NVMe SSD | $0.50 | Recently accessed (10+ hits) | 24 hours |
-| Warm | SSD | $0.15 | Moderate access (3+ hits) | 7 days |
-| Cold | HDD | $0.05 | Occasional access | 30 days |
-| Archive | S3 Glacier | $0.004 | Rarely accessed | 90+ days |
-
-**Background workers continuously:**
-- Monitor access patterns
-- Promote frequently-accessed blobs to faster tiers
-- Demote cold blobs to cheaper storage
-- Optimize cost vs. performance automatically
-
-**Example cost savings for 100TB:**
-- All NVMe: $50,000/month
-- Tiered (10% hot, 20% warm, 40% cold, 30% archive): **$10,120/month** (80% reduction)
-
-#### 5. Storage Backend
-
-**Filesystem-based, not key-value stores**
-
-**Why filesystem instead of BadgerDB/Pebble/etc?**
-- No size limits (TB+ blobs)
-- Native streaming via `io.Reader`/`io.Writer`
-- OS page cache provides automatic hot-data caching
-- Standard tools work (`du`, `find`, `rsync`)
-- Simple debugging and operations
-- Perfect range request support
-- Works identically across all storage tiers
-
-Blobs stored content-addressed:
-```
-/var/cache/oci/
- sha256/
- abc123def456... # blob content
- abc123def456....descriptor.json # metadata
-```
-
-Each storage tier (hot/warm/cold) is a separate filesystem mount with different backing storage.
-
-### Request Flow
-
-#### Cache Hit (Blob Exists)
-```
-1. Client → Load Balancer → Any Cache Pod
-2. Pod queries Redis: "Where is sha256:abc...?"
-3. Redis returns: [pod-2 (hot tier), pod-5 (warm tier)]
-4. Pod issues 307 redirect to pod-2
-5. Client fetches directly from pod-2
-6. Pod-2 streams from local NVMe
-7. Access recorded in Redis (async)
-```
-
-#### Cache Miss (Fetch from Upstream)
-```
-1. Client → Cache Pod → Redis lookup → Not found
-2. Pod acquires distributed lock in Redis for digest
-3. Lock acquired → fetch from upstream begins
-4. Redis metadata queried for optimal placement
-5. Selected pods: [pod-7 (hot), pod-3 (hot)] based on capacity
-6. Blob streams from upstream → client (via pod)
-7. Simultaneously streams to pod-7 and pod-3 storage
-8. Metadata written to Redis on completion
-9. Lock released
-10. Future requests redirect to pod-7 or pod-3
-```
-
-#### Thundering Herd Prevention
-```
-1. Pod A: Acquires lock for sha256:abc... → SUCCESS
-2. Pod B: Attempts lock for sha256:abc... → FAIL
-3. Pod B: Returns 503 Retry-After: 5
-4. Client waits 5 seconds, retries
-5. Blob now cached, Pod B redirects to owner
-```
-
-The lock uses heartbeat extension: acquired with 30s TTL, extended every 10s while download progresses. If the downloading pod crashes, lock expires automatically.
-
-### Multiple Upstream Registries
-
-Barnacle proxies to any number of upstream registries simultaneously:
-
-**Supported upstreams:**
-- Docker Hub (`registry-1.docker.io`)
-- Google Container Registry (`gcr.io`, `us.gcr.io`, etc.)
-- AWS Elastic Container Registry (ECR)
-- Azure Container Registry (ACR)
-- GitHub Container Registry (`ghcr.io`)
-- Harbor
-- Quay.io
-- Any OCI-compliant registry
-
-**Configuration:**
-
-Upstreams are configured as a map where each key is an alias used in the URL path:
-
-```yaml
-upstreams:
- dockerhub:
- registry: registry-1.docker.io
- authentication:
- basic:
- username: "company-bot"
- password: "secret123"
-
- gcr:
- registry: gcr.io
- authentication:
- anonymous: {}
-
- private-registry:
- registry: registry.example.com
- authentication:
- bearer:
- token: "my-token"
-```
-
-**Authentication types:**
-
-- **Basic auth**: Username and password credentials (`authentication.basic`)
-- **Bearer token**: Static bearer token (`authentication.bearer.token`)
-- **Anonymous**: No authentication required (`authentication.anonymous: {}`)
-
-The upstream alias (e.g., `dockerhub`) is used in request URLs: `/v2/dockerhub/library/nginx/manifests/latest`
-
-### Distributed Locking
-
-Redis-based distributed locking prevents multiple pods from fetching the same blob:
-
-**Lock characteristics:**
-- 30-second TTL with 10-second heartbeat extension
-- Progress tracking: only extends if bytes are flowing
-- Automatic expiration if holder crashes
-- Waiters poll and retry with exponential backoff
-
-**Lock lifecycle:**
-```go
-1. Acquire lock with 30s TTL
-2. Start download
-3. Heartbeat goroutine extends every 10s
-4. Track progress (bytes read)
-5. Only extend if making progress
-6. On completion or error: release lock
-7. If crash: lock expires in 30s
-```
-
-Stalled downloads (network issue, upstream timeout) automatically release locks when progress stops, allowing other pods to retry.
-
-### Scalability
-
-**Horizontal scaling:**
-- Add pods → increase capacity and throughput
-- New pods register in Redis membership
-- Placement algorithm automatically uses new capacity
-- No rebalancing required (optional background optimization)
-
-**Storage capacity scaling:**
-```
-3 pods × 5TB PVCs = 15TB total
-Replication factor = 2
-Effective capacity = 7.5TB
-
-Add 2 pods (5 total):
-5 pods × 5TB = 25TB total
-Effective capacity = 12.5TB
-```
-
-**Performance characteristics:**
-- Most requests: Single redirect (307) → direct to owner
-- Cache miss: Single fetch → distributed write → cached
-- Lock contention: Only during cache misses for same digest
-- Metadata lookups: Redis (sub-millisecond)
-
-## Roadmap
-
-### Current Status: v0.1.0 (Alpha) - Feature Complete
-
-The v0.1.0 milestone is feature complete. The project remains in alpha status as we continue stabilization and testing.
-
-✅ **v0.1.0 (Feature Complete):**
-- Read-only proxy mode
-- Metadata-based sharding
-- Redis metadata store
-- Distributed locking
-- Multiple upstream support
-- Hardcoded credentials
-- Storage tiering architecture
-- Background tier migration
-
-**v0.2.0 - Authentication Rework**
-- Credential passthrough (forward client credentials to upstream)
-- GCR service account authentication
-- ECR IAM authentication
-- Pluggable auth provider interface
-- Helm chart
-
-**v0.3.0 - Monitoring & Observability**
-- Prometheus metrics
-- Grafana dashboards
-- Distributed tracing (OpenTelemetry)
-- Structured logging improvements
-- Health check enhancements
-
-**v0.4.0 - Image Optimization**
-- eStargz lazy-pulling support
-- eStargz rewriting for non-optimized images
-- Range request support (HTTP 206 Partial Content)
-- Accept header content negotiation
-- Full OCI Distribution Spec conformance
-
-**v0.5.0 - Operations & Beta Stabilization**
-- Write support (push to cache)
-- Garbage collection
-- Admin API
-- Beta release milestone
-
-**v0.6.0 - Advanced Placement**
-- Multi-factor placement scoring (capacity, load, blob count)
-- Configurable replication factor
-- Multi-cluster federation
-- Cross-region replication
-- Advanced placement policies (cost optimization)
-- Webhook for external placement decisions
-
-**v1.0.0:**
-- Production hardening
-- Performance optimization
-- Comprehensive documentation
-- Reference architectures
-
-## Design Philosophy
-
-### Why These Choices?
-
-**Metadata-driven over consistent hashing:**
-- Flexibility > Simplicity
-- Enables tiering, rebalancing, and capacity-aware placement
-- Slight complexity increase worth the operational benefits
-
-**Redis over etcd/Consul:**
-- Performance: Faster for hot-path lookups
-- Simplicity: Single dependency for metadata + locking
-- Familiarity: Most teams already run Redis
-
-**Filesystem over key-value stores:**
-- No size limits for TB-scale blobs
-- Streaming without memory pressure
-- Operational simplicity (standard tools work)
-
-**Tiering over uniform storage:**
-- 80% cost savings in practice
-- Performance where it matters (hot tier)
-- Automatic optimization reduces ops burden
-
-**Read-only first:**
-- Safer for production introduction
-- Most use cases are pull-heavy
-- Architecture supports writes when ready
-
-## Contributing
-
-Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
-
-**Areas we'd love help:**
-- Performance testing at scale
-- Additional upstream registry integrations
-- Metrics and dashboards
-- Documentation improvements
-- Bug reports and feature requests
-
-## License
-
-Apache License 2.0 - See [LICENSE](LICENSE) for details.
-
-## Acknowledgments
-
-Inspired by:
-- [Docker Distribution](https://github.com/distribution/distribution) - Registry implementation
-- [Groupcache](https://github.com/golang/groupcache) - Distributed caching concepts
-- [Harbor](https://goharbor.io/) - Enterprise registry features
-- The barnacle - Nature's original attachment-based caching system
+
+
+
GoReleaser
+ Release engineering, simplified.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
---
-**Project Status:** Alpha (v0.1.0 feature complete) - Not recommended for production use yet. APIs may change. Beta planned for v0.5.0.
+We handle the complexities of releasing so you can focus in building what really
+matters: **your software**.
-**Questions?** Open an issue.
+
---
-## TODO: OCI Distribution Spec Conformance
+## Get GoReleaser
-The current distribution API implementation is **partially compliant** with the OCI Distribution Specification for read-only operations.
+- [On your machine](https://goreleaser.com/install/);
+- [On CI/CD systems](https://goreleaser.com/ci/).
-### Compliant
+## Documentation
-- HTTP Methods (GET/HEAD) - Correct
-- Error Response Format - OCI-compliant JSON structure with code, message, detail
-- Error Codes - All standard codes defined (BLOB_UNKNOWN, MANIFEST_UNKNOWN, etc.)
-- Status Codes - Correct (200, 400, 404)
-- Tag vs Digest Handling - Correctly uses `:` for tags, `@` for digests
-- Blob Streaming - Efficient io.ReadCloser streaming
-- Required Headers - Docker-Distribution-API-Version, Docker-Content-Digest, Content-Type, Content-Length
+Documentation is hosted live at https://goreleaser.com
-### Intentional Deviation
+## Community
-**Non-Standard URL Pattern:**
-```
-Current: /v2/:upstream/:repository/manifests/:reference
-OCI Spec: /v2//manifests/
-```
+You have questions, need support and or just want to talk about GoReleaser?
-This is **by design** - Barnacle routes to multiple upstream registries, requiring the upstream parameter. Standard OCI clients (docker, containerd, skopeo) cannot use this API directly without configuration.
+Here are ways to get in touch with the GoReleaser community:
-### Missing Features (TODO)
+[](https://discord.gg/RGEBtg8vQ6)
+[](https://twitter.com/goreleaser)
+[](https://github.com/goreleaser/goreleaser/discussions)
-| Feature | Impact | Priority |
-|---------|--------|----------|
-| Accept header content negotiation | Can't negotiate manifest format (image vs list) | Medium |
-| Range request support (206 Partial Content) | Can't resume interrupted blob downloads | Low |
-| Accept-Ranges header on blob endpoints | Clients don't know ranges are unsupported | Low |
-| Content-Type header on HEAD blob | Minor, not strictly required by spec | Low |
+You can find the links above and all others [here](https://goreleaser.com/links/).
+
+### Code of Conduct
+
+This project adheres to the Contributor Covenant [code of conduct](https://github.com/goreleaser/.github/blob/main/CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code.
+We appreciate your contribution.
+Please refer to our [contributing guidelines](CONTRIBUTING.md) for further information.
+
+## Badges
+
+[](https://github.com/goreleaser/goreleaser/releases/latest)
+[](/LICENSE.md)
+[](https://github.com/goreleaser/goreleaser/actions?workflow=build)
+[](https://codecov.io/gh/goreleaser/goreleaser)
+[](https://artifacthub.io/packages/search?repo=goreleaser)
+[](http://godoc.org/github.com/goreleaser/goreleaser)
+[](https://github.com/goreleaser)
+[](https://opencollective.com/goreleaser/backers/)
+[](https://opencollective.com/goreleaser/sponsors/)
+[](https://conventionalcommits.org)
+[](https://bestpractices.coreinfrastructure.org/projects/5420)
+[](https://goreportcard.com/report/github.com/goreleaser/goreleaser)
+
+## Sponsors
+
+Does you or your company use GoReleaser?
+
+You can help keep the project bug-free and feature rich by sponsoring the
+project and its maintainers.
+
+You can sponsor GoReleaser via:
+
+- **[GitHub Sponsors](https://github.com/sponsors/caarlos0)**
+- **[OpenCollective](https://opencollective.com/goreleaser)**
+
+A big **thank you** to all current, past, and future sponsors!
+
+
+
+
+### Gold Sponsors
+
+
+
+

+

+
+
+
+### Silver Sponsors
+
+
+
+

+
+
+
+### Bronze Sponsors
+
+
+
+### Backers
+
+- [Marcel Eichner](https://github.com/Ephigenia)
+- [Chatpong Voranartaksorn](https://github.com/psychvc)
+- [Bruno Paz](https://brunopaz.dev/)
+- [Guest](https://opencollective.com/guest-341ba997)
+- [Automatio AI](https://automatio.ai/)
+- [Bileta Avioni](https://biletaavioni.al/)
+- [Jared Allard](https://github.com/jaredallard)
+- [joe miller](https://github.com/joemiller)
+- [Ryan Nixon](https://github.com/taiidani)
+- [Lawrence Gripper](https://github.com/lawrencegripper)
+- [Francis Lavoie](https://github.com/francislavoie)
+- [Nicolas Gotchac](https://github.com/ngotchac)
+- [Ben](https://github.com/iwpnd)
+- [KEINOS](https://github.com/KEINOS)
+- [Eden Zimbelman](https://github.com/zimeg)
+- [Ben Lechlitner](https://github.com/asphaltbuffet)
+- [Santosh Yadav](https://github.com/santoshyadavdev)
+- [Alexey Palazhchenko](https://github.com/AlekSi)
+- [David Birks](https://github.com/dbirks)
+- [Alex Viscreanu](https://github.com/aexvir)
+- [Ethan Li](https://github.com/ethanjli)
+- [Benjamin Kane](https://github.com/bbkane)
+- [Carl Tsai](https://github.com/moonape1226)
+- [David Dymko](https://github.com/ddymko)
+- [Jan De Dobbeleer](https://github.com/JanDeDobbeleer)
+- [Paul Greenberg](https://github.com/greenpau)
+- [Baptiste Canton](https://github.com/batmac)
+- [Andrew](https://github.com/wobondar)
+- [Sidartha Karna](https://github.com/sidarthakarna)
+- [Kazuma Watanabe](https://github.com/wata727)
+- [Joseph Sirianni](https://github.com/jsirianni)
+- [Oleg Balunenko](https://github.com/obalunenko)
+- [^.{5}\s.{2}ram.{3}$](https://github.com/umatare5)
+- [Ethan Troy](https://github.com/ethanolivertroy)
+
+
+
+### Contributors
+
+This project exists thanks to all the people who contribute.
+[Contribution guide](CONTRIBUTING.md).
+
+## Stars
+
+[](https://starchart.cc/goreleaser/goreleaser)
diff --git a/cmd/barnacle/main.go b/cmd/barnacle/main.go
index faaa9ae..54bdb31 100644
--- a/cmd/barnacle/main.go
+++ b/cmd/barnacle/main.go
@@ -6,6 +6,13 @@ import (
"github.com/joho/godotenv"
)
+// Build-time variables injected via ldflags by goreleaser.
+var (
+ version = "dev"
+ commit = "none"
+ date = "unknown"
+)
+
//go:generate swag init -g main.go --parseInternal -d ./cmd/barnacle/,./internal/routes/,./pkg/api/,./internal/tk/httptk/ -o docs
// @title Barnacle API
diff --git a/cmd/barnacle/serve.go b/cmd/barnacle/serve.go
index d0354ab..fd925b3 100644
--- a/cmd/barnacle/serve.go
+++ b/cmd/barnacle/serve.go
@@ -25,7 +25,11 @@ var serveCmd = &cobra.Command{
}
defer tk.IgnoreDeferError(logger.Sync)
- logger.Info("Starting barnacle server")
+ logger.Info("Starting barnacle server",
+ zap.String("version", version),
+ zap.String("commit", commit),
+ zap.String("date", date),
+ )
// Load configuration
config, err := configloader.LoadConfig(configDir, logger)
From 0828e98afc30e0a7d6a15a6695eb2063a1716c21 Mon Sep 17 00:00:00 2001
From: Dylan Ross
Date: Fri, 13 Feb 2026 17:45:05 -0600
Subject: [PATCH 2/3] fix(ci): updated linter action to v9
---
.github/workflows/ci.yml | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index af9efe9..5d423ee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
go-version: "1.25"
- name: golangci-lint
- uses: golangci/golangci-lint-action@v6
+ uses: golangci/golangci-lint-action@v9
with:
version: latest
@@ -36,13 +36,16 @@ jobs:
run: go test -v $(go list ./... | grep -v /test/e2e) -race
build:
- name: Docker Build
+ name: Container Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build Docker image
- uses: docker/build-push-action@v6
+ - uses: actions/setup-go@v5
with:
- context: .
- push: false
+ go-version: "1.25"
+
+ - uses: ko-build/setup-ko@v0.9
+
+ - name: Build container image
+ run: ko build --local ./cmd/barnacle
From cac4e96a9f2de586192fc59115ba879d263a925f Mon Sep 17 00:00:00 2001
From: Dylan Ross
Date: Fri, 13 Feb 2026 17:49:31 -0600
Subject: [PATCH 3/3] fix: added redis service for tests to pass
---
.github/workflows/ci.yml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5d423ee..2d735df 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,6 +25,16 @@ jobs:
test:
name: Test
runs-on: ubuntu-latest
+ services:
+ redis:
+ image: redis:7
+ ports:
+ - 6379:6379
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
steps:
- uses: actions/checkout@v4