Skip to content
This repository was archived by the owner on Jan 24, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Git
.git
.gitignore

# CI/CD (not needed in image)
.github/
.goreleaser.yaml
.golangci.yml
justfile
release-please-config.json
.release-please-manifest.json

# Documentation
docs/
*.md

# Build artifacts
dist/
bin/
coverage.*
profiles/

# Test data
testdata/

# Nix
flake.*

# IDE/Editor
.idea/
.vscode/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

# Claude/AI
.claude/
CLAUDE.md
AGENTS.md

# Demo/examples
demo.tape
install.sh
100 changes: 100 additions & 0 deletions .github/workflows/docker-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Docker CI

on:
push:
branches: [master]
paths:
- "Dockerfile"
- ".dockerignore"
- ".github/workflows/docker-ci.yml"
- "**.go"
- "go.mod"
- "go.sum"
pull_request:
branches: [master]
paths:
- "Dockerfile"
- ".dockerignore"
- ".github/workflows/docker-ci.yml"
- "**.go"
- "go.mod"
- "go.sum"

permissions:
contents: read
security-events: write

jobs:
hadolint:
name: Dockerfile Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run Hadolint
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: Dockerfile
failure-threshold: warning

build-and-scan:
name: Build and Scan
runs-on: ubuntu-latest
needs: hadolint
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Extract metadata
id: meta
run: |
echo "version=$(git describe --tags --always --dirty)" >> $GITHUB_OUTPUT
echo "commit=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT

- name: Build image for scanning
uses: docker/build-push-action@v6
with:
context: .
push: false
load: true
tags: blobber:ci-${{ github.sha }}
build-args: |
VERSION=${{ steps.meta.outputs.version }}
COMMIT=${{ steps.meta.outputs.commit }}
DATE=${{ steps.meta.outputs.date }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: blobber:ci-${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH,MEDIUM

- name: Upload Trivy scan results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif

- name: Fail on critical vulnerabilities
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: blobber:ci-${{ github.sha }}
format: table
exit-code: "1"
severity: CRITICAL,HIGH

- name: Test image
run: |
docker run --rm blobber:ci-${{ github.sha }} version
docker run --rm blobber:ci-${{ github.sha }} --help
143 changes: 143 additions & 0 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: Docker Release

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Tag to release (e.g., v1.0.0)"
required: true
type: string

permissions:
contents: read
packages: write
id-token: write
attestations: write

env:
GHCR_IMAGE: ghcr.io/meigma/blobber
DOCKERHUB_IMAGE: index.docker.io/meigma/blobber

jobs:
build-and-push:
name: Build and Push
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.tag || github.ref }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Determine tag
id: tag
run: |
if [ -n "${{ inputs.tag }}" ]; then
echo "value=${{ inputs.tag }}" >> $GITHUB_OUTPUT
else
echo "value=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.GHCR_IMAGE }}
${{ env.DOCKERHUB_IMAGE }}
tags: |
type=semver,pattern={{version}},value=${{ steps.tag.outputs.value }}
type=semver,pattern={{major}}.{{minor}},value=${{ steps.tag.outputs.value }}
type=semver,pattern={{major}},value=${{ steps.tag.outputs.value }},enable=${{ !startsWith(steps.tag.outputs.value, 'v0.') }}
type=sha,prefix=
type=raw,value=latest

- name: Extract version info
id: version
run: |
if [ -n "${{ inputs.tag }}" ]; then
VERSION="${{ inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
VERSION="${VERSION#v}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "commit=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT

- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
COMMIT=${{ steps.version.outputs.commit }}
DATE=${{ steps.version.outputs.date }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Build provenance attestations
- name: Attest GHCR build provenance
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.GHCR_IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true

- name: Attest Docker Hub build provenance
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.DOCKERHUB_IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true

# SBOM generation and attestation
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.GHCR_IMAGE }}@${{ steps.build.outputs.digest }}
output-file: sbom.spdx.json
upload-artifact: false

- name: Attest GHCR SBOM
uses: actions/attest-sbom@v2
with:
subject-name: ${{ env.GHCR_IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true

- name: Attest Docker Hub SBOM
uses: actions/attest-sbom@v2
with:
subject-name: ${{ env.DOCKERHUB_IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true
56 changes: 56 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# syntax=docker/dockerfile:1

# =============================================================================
# Stage 1: Builder
# =============================================================================
FROM golang:1.25-alpine AS builder

# Install git for version info and ca-certificates for HTTPS
# hadolint ignore=DL3018
RUN apk add --no-cache git ca-certificates

WORKDIR /build

# Copy dependency files first for better layer caching
COPY go.mod go.sum ./
COPY sigstore/go.mod sigstore/go.sum ./sigstore/
RUN go mod download

# Copy source code
COPY . .

# Build arguments for version information
ARG VERSION=dev
ARG COMMIT=unknown
ARG DATE=unknown

# Build the binary with security flags
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w \
-X github.com/meigma/blobber/cmd/blobber/cli.version=${VERSION} \
-X github.com/meigma/blobber/cmd/blobber/cli.commit=${COMMIT} \
-X github.com/meigma/blobber/cmd/blobber/cli.date=${DATE}" \
-trimpath \
-o /blobber \
./cmd/blobber

# =============================================================================
# Stage 2: Runtime
# =============================================================================
FROM gcr.io/distroless/static-debian12:nonroot

# OCI Image Labels
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="blobber" \
org.opencontainers.image.description="Push and pull files to OCI registries" \
org.opencontainers.image.url="https://github.com/meigma/blobber" \
org.opencontainers.image.source="https://github.com/meigma/blobber" \
org.opencontainers.image.documentation="https://blobber.meigma.dev" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="Meigma"

# Copy the binary (distroless already includes CA certificates)
COPY --from=builder /blobber /usr/local/bin/blobber

# nonroot tag already runs as non-root user (65532)
ENTRYPOINT ["/usr/local/bin/blobber"]
Loading