diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7c9e2fe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Keep the build context to what `go build` actually needs. + +# VCS / CI / editor tooling +.git +.github +.claude +.vscode +.idea +.DS_Store + +# Docs, examples, fixtures, generated output +docs +example +output +test +*.md +LICENSE + +# Test sources (ignored by `go build` anyway) +**/*_test.go + +# Local build artifacts / coverage / databases +mmdb-cli +dist +coverage.out +*.out +*.mmdb + +# Release tooling config (not used by the Docker build) +.goreleaser.yml + +# The Docker build files themselves +Dockerfile +.dockerignore diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000..0b08e32 --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,106 @@ +name: Docker Image + +on: + push: + branches: + - main + tags: + - "v*.*.*" + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + packages: write + +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 0 + + # GHCR image always mirrors the repository path, lower-cased. + - name: Resolve image name + id: images + run: echo "ghcr=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" + + - name: Set up QEMU + uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ${{ steps.images.outputs.ghcr }} + # `latest` tracks the main branch + # Release tags publish immutable X.Y.Z tags + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=sha,format=short,enable={{is_default_branch}} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + flavor: | + latest=false + labels: | + org.opencontainers.image.title=mmdb-cli + org.opencontainers.image.description=Command-line toolkit to create, transform, export and inspect MMDB files + + - name: Compute build version + id: version + run: | + if [ "${{ github.ref_type }}" = "tag" ]; then + echo "value=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" + else + echo "value=edge-$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + fi + + - name: Build and push + id: build + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + build-args: | + VERSION=${{ steps.version.outputs.value }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: mode=max + sbom: true + + - name: Summary + if: github.event_name != 'pull_request' + run: | + { + echo "### Docker image published" + echo + echo '```' + echo "${{ steps.meta.outputs.tags }}" + echo '```' + echo + echo "Digest: \`${{ steps.build.outputs.digest }}\`" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d5d1809 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +FROM --platform=${BUILDPLATFORM} golang:1.26-alpine AS build + +# Provided automatically by buildx for each target in the manifest list +ARG TARGETOS +ARG TARGETARCH + +# Version string baked into `mmdb-cli version`. Defaults to the in-repo placeholder +# CI passes the git tag or an "edge-" value +ARG VERSION=v0.0.0 + +WORKDIR /src + +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY . . + +# Mirrors .github/pre-release-hook.sh +RUN if [ "${VERSION}" != "v0.0.0" ]; then \ + sed -i "s/v0.0.0/${VERSION}/" internal/metadata/metadata.go; \ + fi + +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" \ + go build -trimpath -ldflags="-s -w" -o /out/mmdb-cli . + +FROM alpine:3 + +WORKDIR /data + +RUN apk add --no-cache ca-certificates tzdata \ + && adduser -D -u 1000 -h /home/infraz infraz \ + && chown infraz:infraz /data + +COPY --from=build /out/mmdb-cli /usr/local/bin/mmdb-cli + +LABEL org.opencontainers.image.title="mmdb-cli" \ + org.opencontainers.image.description="Command-line toolkit to create, transform, export and inspect MMDB files" \ + org.opencontainers.image.source="https://github.com/InfraZ/mmdb-cli" \ + org.opencontainers.image.documentation="https://docs.infraz.io/docs/mmdb-cli" \ + org.opencontainers.image.licenses="Apache-2.0" + +USER infraz +ENTRYPOINT ["mmdb-cli"] +CMD ["--help"] diff --git a/README.md b/README.md index dab3324..6fdddbd 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,41 @@ brew install infraz/tap/mmdb-cli mmdb-cli --version ``` +## Container Image + +Pre-built multi-arch images (`linux/amd64`, `linux/arm64`) are published to +the GitHub Container Registry: + +| Reference | Points to | +| :-- | :-- | +| `ghcr.io/infraz/mmdb-cli:latest` | Latest commit on `main` | +| `ghcr.io/infraz/mmdb-cli:1.2.3` (also `:1.2`, `:1`) | Tagged release `v1.2.3` | +| `ghcr.io/infraz/mmdb-cli:sha-abc1234` | A specific `main` commit | + +The image `ENTRYPOINT` is `mmdb-cli` and the working directory is `/data`, so +mount your files there: + +```bash +# Inspect a database +docker run --rm -v "$PWD:/data" ghcr.io/infraz/mmdb-cli:latest \ + metadata -i GeoLite2-City.mmdb + +# Generate a database from a JSON dataset (writes back to the host) +docker run --rm -v "$PWD:/data" --user "$(id -u):$(id -g)" \ + ghcr.io/infraz/mmdb-cli:latest \ + generate -i dataset.json -o result.mmdb +``` + +The container runs as a non-root user (UID 1000). When writing files to a +bind mount, add `--user "$(id -u):$(id -g)"` so the output is owned by you. + +### Building the image locally + +```bash +docker build -t mmdb-cli --build-arg VERSION=v0.0.0-dev . +docker run --rm mmdb-cli version +``` + ## Development To get started, clone the repository and run the following commands to download the dependencies: