From e942179c29a4e3f91d2faa141b21199ede295134 Mon Sep 17 00:00:00 2001 From: Neha Sherpa Date: Tue, 27 Jan 2026 11:13:05 -0800 Subject: [PATCH 1/4] feat: Create a dockerfile for cachew --- Dockerfile | 25 +++++++++ Justfile | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 74 +++++++++++++++++++++------ 3 files changed, 232 insertions(+), 15 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c08309f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +ARG ALPINE_VERSION=3.21 +FROM alpine:${ALPINE_VERSION} + +ARG TARGETARCH +ARG TARGETPLATFORM + +SHELL ["/bin/sh", "-o", "pipefail", "-c"] + +# Install runtime dependencies for git operations and TLS +RUN apk add --no-cache ca-certificates git + +# Set working directory (config uses relative paths like ./state/cache) +WORKDIR /app + +# Copy pre-built binary for the target architecture +COPY dist/cachewd-linux-${TARGETARCH} /usr/local/bin/cachewd + +# Copy default configuration file +COPY cachew.hcl /app/cachew.hcl + +# Bind to all interfaces in Docker (can be overridden with CACHEW_BIND env var) +ENV CACHEW_BIND=0.0.0.0:8080 + +ENTRYPOINT ["/usr/local/bin/cachewd"] +CMD ["--config", "/app/cachew.hcl"] diff --git a/Justfile b/Justfile index 9f18a8a..e367eaf 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,17 @@ set positional-arguments := true set shell := ["bash", "-c"] +# Configuration + +VERSION := `git describe --tags --always --dirty 2>/dev/null || echo "dev"` +GIT_COMMIT := `git rev-parse HEAD 2>/dev/null || echo "unknown"` +TAG := `git rev-parse --short HEAD 2>/dev/null || echo "dev"` +RELEASE := "dist" +REGISTRY := env_var_or_default("REGISTRY", "268271485387.dkr.ecr.us-west-2.amazonaws.com") +IMAGE_NAME := "cachew" +IMAGE_REF := REGISTRY + "/" + IMAGE_NAME + ":" + TAG +IMAGE_LATEST := REGISTRY + "/" + IMAGE_NAME + ":latest" + _help: @just -l @@ -18,3 +29,140 @@ fmt: just --unstable --fmt golangci-lint fmt go mod tidy + +# ============================================================================ +# Build +# ============================================================================ + +# Build for current platform +build: + @mkdir -p {{ RELEASE }} + @go build -trimpath -o {{ RELEASE }}/cachewd \ + -ldflags "-s -w -X main.version={{ VERSION }} -X main.gitCommit={{ GIT_COMMIT }}" \ + ./cmd/cachewd + @echo "✓ Built {{ RELEASE }}/cachewd" + +# Build for Linux (current arch) +build-linux: + #!/usr/bin/env bash + set -e + mkdir -p {{ RELEASE }} + ARCH=$(uname -m) + [[ "$ARCH" == "x86_64" ]] && ARCH="amd64" + [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]] && ARCH="arm64" + echo "Building for linux/${ARCH}..." + GOOS=linux GOARCH=${ARCH} go build -trimpath \ + -o {{ RELEASE }}/cachewd-linux-${ARCH} \ + -ldflags "-s -w -X main.version={{ VERSION }} -X main.gitCommit={{ GIT_COMMIT }}" \ + ./cmd/cachewd + echo "✓ Built {{ RELEASE }}/cachewd-linux-${ARCH}" + +# Build all platforms +build-all: + @mkdir -p {{ RELEASE }} + @echo "Building all platforms..." + @GOOS=darwin GOARCH=arm64 go build -trimpath -o {{ RELEASE }}/cachewd-darwin-arm64 -ldflags "-s -w -X main.version={{ VERSION }} -X main.gitCommit={{ GIT_COMMIT }}" ./cmd/cachewd + @GOOS=darwin GOARCH=amd64 go build -trimpath -o {{ RELEASE }}/cachewd-darwin-amd64 -ldflags "-s -w -X main.version={{ VERSION }} -X main.gitCommit={{ GIT_COMMIT }}" ./cmd/cachewd + @GOOS=linux GOARCH=arm64 go build -trimpath -o {{ RELEASE }}/cachewd-linux-arm64 -ldflags "-s -w -X main.version={{ VERSION }} -X main.gitCommit={{ GIT_COMMIT }}" ./cmd/cachewd + @GOOS=linux GOARCH=amd64 go build -trimpath -o {{ RELEASE }}/cachewd-linux-amd64 -ldflags "-s -w -X main.version={{ VERSION }} -X main.gitCommit={{ GIT_COMMIT }}" ./cmd/cachewd + @echo "✓ Built all platforms" + +# ============================================================================ +# Docker +# ============================================================================ + +# Build single-arch Docker image +docker-build: build-linux + #!/usr/bin/env bash + set -e + ARCH=$(uname -m) + [[ "$ARCH" == "x86_64" ]] && ARCH="amd64" + [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]] && ARCH="arm64" + echo "Building Docker image for linux/${ARCH}..." + docker build --platform linux/${ARCH} -t {{ IMAGE_NAME }}:local . + echo "✓ Built {{ IMAGE_NAME }}:local" + +# Build multi-arch Docker image +docker-build-multi: build-all + #!/usr/bin/env bash + set -e + BUILDER="multi-arch-{{ IMAGE_NAME }}" + docker buildx create --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name "$BUILDER" 2>/dev/null || true + docker buildx use "$BUILDER" + echo "Building multi-arch image..." + docker buildx build --platform linux/amd64,linux/arm64 \ + -t {{ IMAGE_REF }} -t {{ IMAGE_LATEST }} . + echo "✓ Built multi-arch image" + +# ============================================================================ +# Run +# ============================================================================ + +# Run natively +run: build + @echo "→ Starting cachew at http://localhost:8080" + @mkdir -p state + @{{ RELEASE }}/cachewd --config cachew.hcl + +# Run in Docker (optionally with debug: just docker-run debug) +docker-run log_level="info": docker-build + @echo "→ Starting cachew at http://localhost:8080 (log-level={{ log_level }})" + @docker run --rm -it -p 8080:8080 -v $(pwd)/state:/app/state --name {{ IMAGE_NAME }} {{ IMAGE_NAME }}:local --log-level={{ log_level }} + +# Clean up build artifacts and Docker images +docker-clean: + @echo "Cleaning..." + @rm -rf {{ RELEASE }} + @docker rmi {{ IMAGE_NAME }}:local 2>/dev/null || true + @docker rmi {{ IMAGE_REF }} 2>/dev/null || true + @docker rmi {{ IMAGE_LATEST }} 2>/dev/null || true + @echo "✓ Cleaned" + +# ============================================================================ +# ECR Push +# ============================================================================ + +# Push to ECR +image-push tag="latest": build-all + #!/usr/bin/env bash + set -e + + # Login to ECR + echo "→ Authenticating to ECR..." + aws-creds sync + export AWS_REGION="us-west-2" + export AWS_PROFILE=block-coder-comp-staging--dx-admin-operator + + AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com" + aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY + aws ecr create-repository --repository-name {{ IMAGE_NAME }} --region $AWS_REGION 2>/dev/null || true + + # Build and push + BUILDER="multi-arch-{{ IMAGE_NAME }}" + docker buildx rm "$BUILDER" 2>/dev/null || true + docker buildx create --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name "$BUILDER" + docker buildx use "$BUILDER" + + TAGS="-t {{ IMAGE_REF }}" + [[ "{{ tag }}" == "latest" ]] && TAGS="$TAGS -t {{ IMAGE_LATEST }}" + + echo "→ Pushing multi-arch image..." + docker buildx build --platform linux/amd64,linux/arm64 $TAGS --push . + echo "✓ Pushed to ECR" + +# Push to ECR (CI - no login) +image-push-ci tag="latest": build-all + #!/usr/bin/env bash + set -e + BUILDER="multi-arch-{{ IMAGE_NAME }}" + docker buildx rm "$BUILDER" 2>/dev/null || true + docker buildx create --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name "$BUILDER" + docker buildx use "$BUILDER" + + TAGS="-t {{ IMAGE_REF }}" + [[ "{{ tag }}" == "latest" ]] && TAGS="$TAGS -t {{ IMAGE_LATEST }}" + + echo "→ Pushing multi-arch image..." + docker buildx build --platform linux/amd64,linux/arm64 $TAGS --push . + echo "✓ Pushed to ECR" diff --git a/README.md b/README.md index 7945b70..efb66ab 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,73 @@ Cachew is a server and tooling for incredibly efficient, protocol-aware caching. designed to be used at scale, with minimal impact on upstream systems. By "protocol-aware", we mean that the proxy isn't just a naive HTTP proxy, it is aware of the higher level protocol being proxied (Git, Docker, etc.) and can make more efficient decisions. -## Git +## Quick Start -Git causes a number of problems for us, but the most obvious are: +### Local Development -1. Rate limiting by service providers. -2. `git clone` is very slow, even discounting network overhead +**Run natively (fastest for development):** +```bash +just run # Build and run on localhost:8080 +``` + +**Run in Docker:** +```bash +just docker-run # Build and run in container +just docker-run debug # Run with debug logging +``` + +### Using the Cache + +The `cachew` CLI client interacts with the `cachewd` server: + +```bash +# Upload to cache +cachew put my-key myfile.txt --ttl 24h + +# Download from cache +cachew get my-key -o myfile.txt -To solve this we apply two different strategies on the server: +# Check if cached +cachew stat my-key -1. Periodic full `.tar.zst` snapshots of the repository. These snapshots restore 4-5x faster than `git clone`. -2. Passthrough caching of the packs returned by `POST /repo.git/git-upload-pack` to support incremental pulls. +# Snapshot a directory +cachew snapshot deps-cache ./node_modules --ttl 7d -On the client we redirect git to the proxy: +# Restore a directory +cachew restore deps-cache ./node_modules +``` + +### Building and Testing -```ini -[url "https://cachew.local/github/"] - insteadOf = https://github.com/ +```bash +just build # Build for current platform +just build-linux # Build for Linux (for Docker) +just build-all # Build all platforms +just test # Run tests +just lint # Lint code +just fmt # Format code ``` -As Git itself isn't aware of the snapshots, Git-specific code in the Cachew CLI can be used to reconstruct a repository. +## Docker Images -## Docker +### Build Images + +```bash +just docker-build # Single-arch local image (fast) +just docker-build-multi # Multi-arch image for ECR +``` + +### Push to ECR + +**Push from local machine:** +```bash +just image-push # Push with commit SHA tag +just image-push latest # Push :latest and commit SHA tags +``` +## Configuration -## Hermit +The default configuration is in `cachew.hcl`. Key settings: -Hermit +- **Disk cache**: `./state/cache` (250GB limit, 8h TTL) +- **Git mirrors**: `./state/git-mirrors` +- **Bind address**: `0.0.0.0:8080` (in Docker), `127.0.0.1:8080` (native) \ No newline at end of file From 140bf18f9dd33a05d8fe0db0a8d378b93c8c6f31 Mon Sep 17 00:00:00 2001 From: Neha Sherpa Date: Tue, 27 Jan 2026 11:13:05 -0800 Subject: [PATCH 2/4] feat: Create a dockerfile for cachew --- CONTRIBUTING.md | 65 +++++++++++++++++++++++++++++++++++++++++++ README.md | 74 ++++++++++--------------------------------------- 2 files changed, 80 insertions(+), 59 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0561d81 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,65 @@ +# Contributing to Cachew + +Thank you for your interest in contributing to Cachew! This guide will help you get started with local development, building, and testing. + +## Local Development + +**Run natively (fastest for development):** +```bash +just run # Build and run on localhost:8080 +``` + +**Run in Docker:** +```bash +just docker-run # Build and run in container +just docker-run debug # Run with debug logging +``` + +## Building and Testing + +```bash +just build # Build for current platform +just build-linux # Build for Linux (for Docker) +just build-all # Build all platforms +just test # Run tests +just lint # Lint code +just fmt # Format code +``` + +## Docker Images + +### Build Images + +```bash +just docker-build # Single-arch local image (fast) +just docker-build-multi # Multi-arch image for ECR +``` + +### Push to ECR + +**Push from local machine:** +```bash +just image-push # Push with commit SHA tag +just image-push latest # Push :latest and commit SHA tags +``` + +## Using the Cache + +The `cachew` CLI client interacts with the `cachewd` server: + +```bash +# Upload to cache +cachew put my-key myfile.txt --ttl 24h + +# Download from cache +cachew get my-key -o myfile.txt + +# Check if cached +cachew stat my-key + +# Snapshot a directory +cachew snapshot deps-cache ./node_modules --ttl 7d + +# Restore a directory +cachew restore deps-cache ./node_modules +``` diff --git a/README.md b/README.md index efb66ab..7945b70 100644 --- a/README.md +++ b/README.md @@ -4,73 +4,29 @@ Cachew is a server and tooling for incredibly efficient, protocol-aware caching. designed to be used at scale, with minimal impact on upstream systems. By "protocol-aware", we mean that the proxy isn't just a naive HTTP proxy, it is aware of the higher level protocol being proxied (Git, Docker, etc.) and can make more efficient decisions. -## Quick Start +## Git -### Local Development +Git causes a number of problems for us, but the most obvious are: -**Run natively (fastest for development):** -```bash -just run # Build and run on localhost:8080 -``` - -**Run in Docker:** -```bash -just docker-run # Build and run in container -just docker-run debug # Run with debug logging -``` - -### Using the Cache - -The `cachew` CLI client interacts with the `cachewd` server: - -```bash -# Upload to cache -cachew put my-key myfile.txt --ttl 24h - -# Download from cache -cachew get my-key -o myfile.txt +1. Rate limiting by service providers. +2. `git clone` is very slow, even discounting network overhead -# Check if cached -cachew stat my-key +To solve this we apply two different strategies on the server: -# Snapshot a directory -cachew snapshot deps-cache ./node_modules --ttl 7d +1. Periodic full `.tar.zst` snapshots of the repository. These snapshots restore 4-5x faster than `git clone`. +2. Passthrough caching of the packs returned by `POST /repo.git/git-upload-pack` to support incremental pulls. -# Restore a directory -cachew restore deps-cache ./node_modules -``` - -### Building and Testing +On the client we redirect git to the proxy: -```bash -just build # Build for current platform -just build-linux # Build for Linux (for Docker) -just build-all # Build all platforms -just test # Run tests -just lint # Lint code -just fmt # Format code +```ini +[url "https://cachew.local/github/"] + insteadOf = https://github.com/ ``` -## Docker Images +As Git itself isn't aware of the snapshots, Git-specific code in the Cachew CLI can be used to reconstruct a repository. -### Build Images - -```bash -just docker-build # Single-arch local image (fast) -just docker-build-multi # Multi-arch image for ECR -``` - -### Push to ECR - -**Push from local machine:** -```bash -just image-push # Push with commit SHA tag -just image-push latest # Push :latest and commit SHA tags -``` -## Configuration +## Docker -The default configuration is in `cachew.hcl`. Key settings: +## Hermit -- **Disk cache**: `./state/cache` (250GB limit, 8h TTL) -- **Git mirrors**: `./state/git-mirrors` -- **Bind address**: `0.0.0.0:8080` (in Docker), `127.0.0.1:8080` (native) \ No newline at end of file +Hermit From 174e925d3ce783b5c0c529a32c4a973dd07ae1f0 Mon Sep 17 00:00:00 2001 From: Neha Sherpa Date: Wed, 28 Jan 2026 10:55:23 -0800 Subject: [PATCH 3/4] fix: Address comments and more fixes --- CONTRIBUTING.md | 25 ++++----- Justfile | 95 ++------------------------------- Dockerfile => docker/Dockerfile | 0 docker/Justfile | 35 ++++++++++++ 4 files changed, 49 insertions(+), 106 deletions(-) rename Dockerfile => docker/Dockerfile (100%) create mode 100644 docker/Justfile diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0561d81..9d7e9f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,36 +11,29 @@ just run # Build and run on localhost:8080 **Run in Docker:** ```bash -just docker-run # Build and run in container -just docker-run debug # Run with debug logging +just docker run # Build and run in container +just docker run debug # Run with debug logging ``` ## Building and Testing ```bash just build # Build for current platform -just build-linux # Build for Linux (for Docker) +just build-linux # Build for Linux just build-all # Build all platforms just test # Run tests just lint # Lint code just fmt # Format code ``` -## Docker Images +## Docker -### Build Images - -```bash -just docker-build # Single-arch local image (fast) -just docker-build-multi # Multi-arch image for ECR -``` - -### Push to ECR - -**Push from local machine:** ```bash -just image-push # Push with commit SHA tag -just image-push latest # Push :latest and commit SHA tags +just docker build # Build single-arch Docker image for local use +just docker build-multi # Build multi-arch image (amd64 + arm64) +just docker run # Run in container +just docker run debug # Run with debug logging +just docker clean # Clean up Docker images ``` ## Using the Cache diff --git a/Justfile b/Justfile index e367eaf..58c7a85 100644 --- a/Justfile +++ b/Justfile @@ -1,16 +1,15 @@ set positional-arguments := true set shell := ["bash", "-c"] +# Import modules +mod docker + # Configuration VERSION := `git describe --tags --always --dirty 2>/dev/null || echo "dev"` GIT_COMMIT := `git rev-parse HEAD 2>/dev/null || echo "unknown"` TAG := `git rev-parse --short HEAD 2>/dev/null || echo "dev"` RELEASE := "dist" -REGISTRY := env_var_or_default("REGISTRY", "268271485387.dkr.ecr.us-west-2.amazonaws.com") -IMAGE_NAME := "cachew" -IMAGE_REF := REGISTRY + "/" + IMAGE_NAME + ":" + TAG -IMAGE_LATEST := REGISTRY + "/" + IMAGE_NAME + ":latest" _help: @just -l @@ -67,33 +66,6 @@ build-all: @GOOS=linux GOARCH=amd64 go build -trimpath -o {{ RELEASE }}/cachewd-linux-amd64 -ldflags "-s -w -X main.version={{ VERSION }} -X main.gitCommit={{ GIT_COMMIT }}" ./cmd/cachewd @echo "✓ Built all platforms" -# ============================================================================ -# Docker -# ============================================================================ - -# Build single-arch Docker image -docker-build: build-linux - #!/usr/bin/env bash - set -e - ARCH=$(uname -m) - [[ "$ARCH" == "x86_64" ]] && ARCH="amd64" - [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]] && ARCH="arm64" - echo "Building Docker image for linux/${ARCH}..." - docker build --platform linux/${ARCH} -t {{ IMAGE_NAME }}:local . - echo "✓ Built {{ IMAGE_NAME }}:local" - -# Build multi-arch Docker image -docker-build-multi: build-all - #!/usr/bin/env bash - set -e - BUILDER="multi-arch-{{ IMAGE_NAME }}" - docker buildx create --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name "$BUILDER" 2>/dev/null || true - docker buildx use "$BUILDER" - echo "Building multi-arch image..." - docker buildx build --platform linux/amd64,linux/arm64 \ - -t {{ IMAGE_REF }} -t {{ IMAGE_LATEST }} . - echo "✓ Built multi-arch image" - # ============================================================================ # Run # ============================================================================ @@ -104,65 +76,8 @@ run: build @mkdir -p state @{{ RELEASE }}/cachewd --config cachew.hcl -# Run in Docker (optionally with debug: just docker-run debug) -docker-run log_level="info": docker-build - @echo "→ Starting cachew at http://localhost:8080 (log-level={{ log_level }})" - @docker run --rm -it -p 8080:8080 -v $(pwd)/state:/app/state --name {{ IMAGE_NAME }} {{ IMAGE_NAME }}:local --log-level={{ log_level }} - -# Clean up build artifacts and Docker images -docker-clean: +# Clean up build artifacts +clean: @echo "Cleaning..." @rm -rf {{ RELEASE }} - @docker rmi {{ IMAGE_NAME }}:local 2>/dev/null || true - @docker rmi {{ IMAGE_REF }} 2>/dev/null || true - @docker rmi {{ IMAGE_LATEST }} 2>/dev/null || true @echo "✓ Cleaned" - -# ============================================================================ -# ECR Push -# ============================================================================ - -# Push to ECR -image-push tag="latest": build-all - #!/usr/bin/env bash - set -e - - # Login to ECR - echo "→ Authenticating to ECR..." - aws-creds sync - export AWS_REGION="us-west-2" - export AWS_PROFILE=block-coder-comp-staging--dx-admin-operator - - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) - ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com" - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY - aws ecr create-repository --repository-name {{ IMAGE_NAME }} --region $AWS_REGION 2>/dev/null || true - - # Build and push - BUILDER="multi-arch-{{ IMAGE_NAME }}" - docker buildx rm "$BUILDER" 2>/dev/null || true - docker buildx create --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name "$BUILDER" - docker buildx use "$BUILDER" - - TAGS="-t {{ IMAGE_REF }}" - [[ "{{ tag }}" == "latest" ]] && TAGS="$TAGS -t {{ IMAGE_LATEST }}" - - echo "→ Pushing multi-arch image..." - docker buildx build --platform linux/amd64,linux/arm64 $TAGS --push . - echo "✓ Pushed to ECR" - -# Push to ECR (CI - no login) -image-push-ci tag="latest": build-all - #!/usr/bin/env bash - set -e - BUILDER="multi-arch-{{ IMAGE_NAME }}" - docker buildx rm "$BUILDER" 2>/dev/null || true - docker buildx create --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name "$BUILDER" - docker buildx use "$BUILDER" - - TAGS="-t {{ IMAGE_REF }}" - [[ "{{ tag }}" == "latest" ]] && TAGS="$TAGS -t {{ IMAGE_LATEST }}" - - echo "→ Pushing multi-arch image..." - docker buildx build --platform linux/amd64,linux/arm64 $TAGS --push . - echo "✓ Pushed to ECR" diff --git a/Dockerfile b/docker/Dockerfile similarity index 100% rename from Dockerfile rename to docker/Dockerfile diff --git a/docker/Justfile b/docker/Justfile new file mode 100644 index 0000000..7404130 --- /dev/null +++ b/docker/Justfile @@ -0,0 +1,35 @@ +set positional-arguments := true +set shell := ["bash", "-c"] + +# Configuration +ROOT := `git rev-parse --show-toplevel 2>/dev/null || echo "."` +TAG := `git rev-parse --short HEAD 2>/dev/null || echo "dev"` +DOCKERFILE := ROOT + "/docker/Dockerfile" + +# Build Docker image for local development +build: + @just --justfile {{ ROOT }}/Justfile build-linux + @echo "Building Docker image..." + @docker build -f {{ DOCKERFILE }} -t cachew:local {{ ROOT }} + @echo "✓ Built cachew:local" + +# Build multi-arch Docker image (builds for amd64 + arm64, stays in cache) +build-multi: + @just --justfile {{ ROOT }}/Justfile build-all + @docker buildx create --use --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name multi-arch-cachew 2>/dev/null || docker buildx use multi-arch-cachew + @echo "Building multi-arch image..." + @docker buildx build --platform linux/amd64,linux/arm64 -f {{ DOCKERFILE }} -t cachew:{{ TAG }} {{ ROOT }} + @echo "✓ Built multi-arch image (in cache)" + +# Run in Docker (usage: just docker run [log_level]) +run log_level="info": + @just build + @echo "→ Starting cachew at http://localhost:8080 (log-level={{ log_level }})" + @docker run --rm -it -p 8080:8080 -v {{ ROOT }}/state:/app/state --name cachew cachew:local --log-level={{ log_level }} + +# Clean up Docker images +clean: + @echo "Cleaning Docker images..." + @docker rmi cachew:local 2>/dev/null || true + @docker rmi cachew:{{ TAG }} 2>/dev/null || true + @echo "✓ Cleaned" From 1f5d5c4d1f511aa48a8262737c445701c5d7ed9d Mon Sep 17 00:00:00 2001 From: Neha Sherpa Date: Wed, 28 Jan 2026 12:46:58 -0800 Subject: [PATCH 4/4] feat: Push to ghcr.io --- .dockerignore | 86 +++++++++++++++++++++++++++ .github/workflows/publish-docker.yml | 89 ++++++++++++++++++++++++++++ docker/Dockerfile | 44 ++++++++++++-- docker/Justfile | 18 ++++-- 4 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/publish-docker.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6ac986d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,86 @@ +# Build artifacts +dist/ +**/dist/ + +# Git +.git/ +.gitignore + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Environment and config files +.env +.env.* +*.log + +# CI/CD +.github/ +.hermit/ +bin/ + +# Temporary files +tmp/ +temp/ +*.tmp + +# Test coverage +coverage/ +*.lcov + +# Documentation +docs/ +*.md +!README.md + +# Development scripts +scripts/ + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Windows +Thumbs.db +ehthumbs.db + +# Linux +.directory +.Trash-* + +# Archives +*.tar +*.tar.gz +*.tar.bz2 +*.zip +*.7z +*.rar + +# Backup files +*.bak +*.backup +*.old + +# Docker files (don't need to copy these into context) +docker/ +Dockerfile +.dockerignore +docker-compose.yml + +# Development tools +Justfile +lefthook.yml +renovate.json +.goreleaser.yaml +.golangci.yml +CODEOWNERS +CONTRIBUTING.md +GOVERNANCE.md +AGENTS.md +LICENSE diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml new file mode 100644 index 0000000..eb2ccd4 --- /dev/null +++ b/.github/workflows/publish-docker.yml @@ -0,0 +1,89 @@ +name: Publish Docker Image + +on: + workflow_run: + workflows: ["CI"] + types: + - completed + branches: + - main + - docker + push: + tags: + - 'v*.*.*' + - 'v*.*.*-*' # For pre-releases like v1.2.3-beta + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + docker: + runs-on: ubuntu-latest + # Only run if CI passed (when triggered by workflow_run) or on tag push or manual dispatch + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'push' || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # pin@v3.11.1 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # pin@v3.5.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version metadata + id: meta + run: | + VERSION="${GITHUB_REF#refs/tags/}" + if [[ "$VERSION" == refs/heads/* ]]; then + VERSION="${VERSION#refs/heads/}" + fi + if [[ "$VERSION" == "main" ]]; then + VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" + fi + echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" + + GIT_COMMIT="$(git rev-parse HEAD)" + echo "git_commit=${GIT_COMMIT}" >> "${GITHUB_OUTPUT}" + + - name: Extract Docker metadata + id: docker_meta + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # pin@v5.8.0 + with: + images: ghcr.io/${{ github.repository_owner }}/cachew + tags: | + # For main branch: latest, main, and sha + type=ref,event=branch + type=raw,value=latest,enable={{is_default_branch}} + # For tags: v1.2.3 -> 1.2.3, 1.2, 1 + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + # For pre-release tags: keep the full version + type=raw,value={{tag}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + + - name: Build and push Docker image + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # pin@v6.18.0 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + build-args: | + VERSION=${{ steps.meta.outputs.version }} + GIT_COMMIT=${{ steps.meta.outputs.git_commit }} diff --git a/docker/Dockerfile b/docker/Dockerfile index c08309f..7ef8329 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,23 +1,55 @@ -ARG ALPINE_VERSION=3.21 -FROM alpine:${ALPINE_VERSION} +# Build stage +FROM golang:1.25.5-alpine AS builder +ARG VERSION=dev +ARG GIT_COMMIT=unknown +ARG TARGETOS ARG TARGETARCH -ARG TARGETPLATFORM + +WORKDIR /build + +# Install build dependencies +RUN apk add --no-cache git ca-certificates tzdata + +# Copy go mod files first for better caching +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source code +COPY . . + +# Build the binary +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build -trimpath -o cachewd \ + -ldflags "-s -w -X main.version=${VERSION} -X main.gitCommit=${GIT_COMMIT}" \ + ./cmd/cachewd + +# Runtime stage +FROM alpine:3.21 SHELL ["/bin/sh", "-o", "pipefail", "-c"] # Install runtime dependencies for git operations and TLS -RUN apk add --no-cache ca-certificates git +RUN apk add --no-cache ca-certificates git tzdata && \ + addgroup -g 1000 cachew && \ + adduser -D -u 1000 -G cachew cachew # Set working directory (config uses relative paths like ./state/cache) WORKDIR /app -# Copy pre-built binary for the target architecture -COPY dist/cachewd-linux-${TARGETARCH} /usr/local/bin/cachewd +# Copy binary from builder +COPY --from=builder /build/cachewd /usr/local/bin/cachewd # Copy default configuration file COPY cachew.hcl /app/cachew.hcl +# Create state directory with proper permissions +RUN mkdir -p /app/state/cache && \ + chown -R cachew:cachew /app + +# Switch to non-root user +USER cachew + # Bind to all interfaces in Docker (can be overridden with CACHEW_BIND env var) ENV CACHEW_BIND=0.0.0.0:8080 diff --git a/docker/Justfile b/docker/Justfile index 7404130..d6e4704 100644 --- a/docker/Justfile +++ b/docker/Justfile @@ -4,21 +4,31 @@ set shell := ["bash", "-c"] # Configuration ROOT := `git rev-parse --show-toplevel 2>/dev/null || echo "."` TAG := `git rev-parse --short HEAD 2>/dev/null || echo "dev"` +VERSION := `git describe --tags --always --dirty 2>/dev/null || echo "dev"` +GIT_COMMIT := `git rev-parse HEAD 2>/dev/null || echo "unknown"` DOCKERFILE := ROOT + "/docker/Dockerfile" # Build Docker image for local development build: - @just --justfile {{ ROOT }}/Justfile build-linux @echo "Building Docker image..." - @docker build -f {{ DOCKERFILE }} -t cachew:local {{ ROOT }} + @docker build -f {{ DOCKERFILE }} \ + --build-arg VERSION={{ VERSION }} \ + --build-arg GIT_COMMIT={{ GIT_COMMIT }} \ + -t cachew:local \ + {{ ROOT }} @echo "✓ Built cachew:local" # Build multi-arch Docker image (builds for amd64 + arm64, stays in cache) build-multi: - @just --justfile {{ ROOT }}/Justfile build-all @docker buildx create --use --driver docker-container --driver-opt image=moby/buildkit:v0.17.3 --name multi-arch-cachew 2>/dev/null || docker buildx use multi-arch-cachew @echo "Building multi-arch image..." - @docker buildx build --platform linux/amd64,linux/arm64 -f {{ DOCKERFILE }} -t cachew:{{ TAG }} {{ ROOT }} + @docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -f {{ DOCKERFILE }} \ + --build-arg VERSION={{ VERSION }} \ + --build-arg GIT_COMMIT={{ GIT_COMMIT }} \ + -t cachew:{{ TAG }} \ + {{ ROOT }} @echo "✓ Built multi-arch image (in cache)" # Run in Docker (usage: just docker run [log_level])