From f6d0470f1156ff0d1233af6877c8ff450d3ef076 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:27:09 -0600 Subject: [PATCH 01/16] Modernize Docker build with parameterization and registry flexibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major improvements: - Add full parameterization (registry, namespace, image name) - Auto-detect registry: temporalio → docker.io, others → ghcr.io - Separate workflow for managing 'latest' tag on release events - Dynamic Docker labels using GITHUB_REPOSITORY variable - Add packages:write permission for GHCR - Remove artifact uploading (no longer needed) Benefits: - Works out-of-box for both upstream and forks - Flexible registry support (Docker Hub, GHCR, any registry) - Clean separation of release vs latest-tag concerns - Proper package association in GitHub Configuration requirements: - DOCKER_USERNAME and DOCKER_PASSWORD secrets needed for Docker Hub - GITHUB_TOKEN automatically provides GHCR access --- .github/docker/docker-bake.hcl | 24 +++-- .github/workflows/build-and-publish.yml | 115 ++++++++++++++++------- .github/workflows/goreleaser.yml | 1 + .github/workflows/update-latest-tag.yml | 120 ++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/update-latest-tag.yml diff --git a/.github/docker/docker-bake.hcl b/.github/docker/docker-bake.hcl index e1818912b..0f158496c 100644 --- a/.github/docker/docker-bake.hcl +++ b/.github/docker/docker-bake.hcl @@ -1,5 +1,17 @@ variable "IMAGE_REPO" { - default = "temporalio" + default = "ghcr.io" +} + +variable "IMAGE_NAMESPACE" { + default = "" +} + +variable "IMAGE_NAME" { + default = "temporal" +} + +variable "GITHUB_REPOSITORY" { + default = "temporalio/cli" } variable "IMAGE_SHA_TAG" {} @@ -27,9 +39,9 @@ target "cli" { dockerfile = ".github/docker/cli.Dockerfile" context = "." tags = compact([ - "${IMAGE_REPO}/temporal:${IMAGE_SHA_TAG}", - "${IMAGE_REPO}/temporal:${VERSION}", - TAG_LATEST ? "${IMAGE_REPO}/temporal:latest" : "", + IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", + IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}", + TAG_LATEST ? (IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest") : "", ]) platforms = ["linux/amd64", "linux/arm64"] args = { @@ -38,8 +50,8 @@ target "cli" { labels = { "org.opencontainers.image.title" = "temporal" "org.opencontainers.image.description" = "Temporal CLI" - "org.opencontainers.image.url" = "https://github.com/temporalio/cli" - "org.opencontainers.image.source" = "https://github.com/temporalio/cli" + "org.opencontainers.image.url" = "https://github.com/${GITHUB_REPOSITORY}" + "org.opencontainers.image.source" = "https://github.com/${GITHUB_REPOSITORY}" "org.opencontainers.image.licenses" = "MIT" "org.opencontainers.image.revision" = "${CLI_SHA}" "org.opencontainers.image.created" = timestamp() diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index d60fcc37a..9cc7b646a 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -11,6 +11,21 @@ on: description: "Version tag for the release (required if publish is true)" required: false type: string + registry: + description: "Container registry (docker.io, ghcr.io, etc.)" + required: false + type: string + default: "" + registry_namespace: + description: "Registry namespace/organization" + required: false + type: string + default: "" + image_name: + description: "Image name" + required: false + type: string + default: "temporal" secrets: DOCKER_USERNAME: required: false @@ -84,18 +99,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub - if: inputs.publish && github.repository_owner == 'temporalio' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Get build metadata id: meta env: INPUT_VERSION: ${{ inputs.version }} INPUT_PUBLISH: ${{ inputs.publish }} + INPUT_REGISTRY: ${{ inputs.registry }} + INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} + INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} run: | echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -111,30 +122,64 @@ jobs: echo "version=snapshot" >> $GITHUB_OUTPUT fi - # Determine image repo based on repository owner - if [[ "$REPO_OWNER" == "temporalio" ]]; then - echo "image_repo=temporalio" >> $GITHUB_OUTPUT + # Determine registry (with auto-detection for temporalio vs forks) + REGISTRY="$INPUT_REGISTRY" + if [[ -z "$REGISTRY" ]]; then + if [[ "$REPO_OWNER" == "temporalio" ]]; then + REGISTRY="docker.io" + else + REGISTRY="ghcr.io" + fi + fi + + # Determine registry type for authentication + if [[ "$REGISTRY" == "ghcr.io" ]]; then + echo "registry_type=ghcr" >> $GITHUB_OUTPUT + elif [[ "$REGISTRY" == "docker.io" ]]; then + echo "registry_type=dockerhub" >> $GITHUB_OUTPUT else - echo "image_repo=$REPO_OWNER" >> $GITHUB_OUTPUT + echo "registry_type=other" >> $GITHUB_OUTPUT fi - - name: Check if release is latest - if: inputs.publish - id: check_latest - uses: actions/github-script@v7 + # Set namespace (defaults to repository owner) + NAMESPACE="$INPUT_REGISTRY_NAMESPACE" + if [[ -z "$NAMESPACE" ]]; then + NAMESPACE="$REPO_OWNER" + fi + + # Set image name (defaults to 'temporal') + IMAGE_NAME="$INPUT_IMAGE_NAME" + if [[ -z "$IMAGE_NAME" ]]; then + IMAGE_NAME="temporal" + fi + + # For Docker Hub, use empty string as registry (special case) + if [[ "$REGISTRY" == "docker.io" ]]; then + echo "image_repo=" >> $GITHUB_OUTPUT + else + echo "image_repo=${REGISTRY}" >> $GITHUB_OUTPUT + fi + + echo "image_namespace=${NAMESPACE}" >> $GITHUB_OUTPUT + echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + if: inputs.publish && steps.meta.outputs.registry_type == 'ghcr' + uses: docker/login-action@v3 with: - script: | - const { data: release } = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: context.ref.replace('refs/tags/', '') - }); - // Tag as latest only if release is marked as latest (not pre-release) - core.setOutput('tag_latest', release.prerelease ? 'false' : 'true'); - console.log(`Release prerelease: ${release.prerelease}, tag_latest: ${!release.prerelease}`) + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: inputs.publish && steps.meta.outputs.registry_type == 'dockerhub' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image - if: inputs.publish && github.repository_owner == 'temporalio' + if: inputs.publish run: | docker buildx bake \ --file .github/docker/docker-bake.hcl \ @@ -145,8 +190,11 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest.outputs.tag_latest }} - IMAGE_REPO: temporalio + TAG_LATEST: false + IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} + IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} + IMAGE_NAME: ${{ steps.meta.outputs.image_name }} + GITHUB_REPOSITORY: ${{ github.repository }} - name: Build Docker image if: ${{ !inputs.publish }} @@ -160,12 +208,7 @@ jobs: IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} TAG_LATEST: false - IMAGE_REPO: temporalio - - - name: Upload build artifacts - if: ${{ !inputs.publish }} - uses: actions/upload-artifact@v4 - with: - name: temporal-cli-dist - path: dist/ - retention-days: 7 + IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} + IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} + IMAGE_NAME: ${{ steps.meta.outputs.image_name }} + GITHUB_REPOSITORY: ${{ github.repository }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index dfd79cedd..100129438 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -8,6 +8,7 @@ on: permissions: contents: write + packages: write jobs: release: diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml new file mode 100644 index 000000000..d8a73cba2 --- /dev/null +++ b/.github/workflows/update-latest-tag.yml @@ -0,0 +1,120 @@ +name: Update Latest Docker Tag + +on: + release: + types: + - edited + - released + +permissions: + contents: read + packages: write + +jobs: + update-latest: + name: Update Latest Tag + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Check if release is latest + id: check_latest + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + uses: actions/github-script@v7 + with: + script: | + const releaseTag = process.env.RELEASE_TAG; + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: releaseTag + }); + + const isLatest = !release.prerelease && !release.draft; + core.setOutput('is_latest', isLatest); + console.log(`Release: ${release.tag_name}`); + console.log(`Prerelease: ${release.prerelease}, Draft: ${release.draft}`); + console.log(`Should tag as latest: ${isLatest}`); + + - name: Set up Docker Buildx + if: steps.check_latest.outputs.is_latest == 'true' + uses: docker/setup-buildx-action@v3 + + - name: Get registry configuration + if: steps.check_latest.outputs.is_latest == 'true' + id: registry + run: | + REPO_OWNER="${{ github.repository_owner }}" + + # Auto-detect registry based on repository owner + if [[ "$REPO_OWNER" == "temporalio" ]]; then + REGISTRY="docker.io" + echo "type=dockerhub" >> $GITHUB_OUTPUT + echo "repo=" >> $GITHUB_OUTPUT + else + REGISTRY="ghcr.io" + echo "type=ghcr" >> $GITHUB_OUTPUT + echo "repo=${REGISTRY}" >> $GITHUB_OUTPUT + fi + + echo "namespace=${REPO_OWNER}" >> $GITHUB_OUTPUT + echo "image=temporal" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'ghcr' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'dockerhub' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Get version tag + if: steps.check_latest.outputs.is_latest == 'true' + id: version + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + VERSION="$RELEASE_TAG" + VERSION="${VERSION#v}" + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + - name: Pull and retag image as latest + if: steps.check_latest.outputs.is_latest == 'true' + run: | + # Construct image paths + REPO="${{ steps.registry.outputs.repo }}" + NAMESPACE="${{ steps.registry.outputs.namespace }}" + IMAGE="${{ steps.registry.outputs.image }}" + VERSION="${{ steps.version.outputs.version }}" + + if [[ -z "$REPO" ]]; then + # Docker Hub format + SOURCE_IMAGE="${NAMESPACE}/${IMAGE}:${VERSION}" + LATEST_IMAGE="${NAMESPACE}/${IMAGE}:latest" + else + # Other registries + SOURCE_IMAGE="${REPO}/${NAMESPACE}/${IMAGE}:${VERSION}" + LATEST_IMAGE="${REPO}/${NAMESPACE}/${IMAGE}:latest" + fi + + echo "Pulling ${SOURCE_IMAGE}..." + docker pull ${SOURCE_IMAGE} + + echo "Tagging as ${LATEST_IMAGE}..." + docker tag ${SOURCE_IMAGE} ${LATEST_IMAGE} + + echo "Pushing ${LATEST_IMAGE}..." + docker push ${LATEST_IMAGE} + + echo "✅ Successfully updated latest tag to point to version ${VERSION}" From bda2d14fd7f22fa2b7a8a077283ede3624a949e1 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:52:37 -0600 Subject: [PATCH 02/16] Address PR feedback: simplify Dockerfile and workflows - Move Dockerfile to repo root and simplify to single-stage Alpine build - Remove complex multi-stage build and ARG ALPINE_IMAGE parameterization - Change USER 1000:1000 to USER temporal:temporal for readability - Delete Makefile entirely (not needed in this repo) - Update GitHub Actions to use major version tags instead of fixed hashes - Convert bash registry logic to JavaScript in update-latest-tag.yml - Keep fork support logic for temporalio vs ghcr.io auto-detection - Update docker-bake.hcl to reference new Dockerfile location - Test Docker build functionality - all working correctly --- .github/docker/docker-bake.hcl | 10 ++---- .github/workflows/build-and-publish.yml | 4 +-- .github/workflows/update-latest-tag.yml | 41 +++++++++++++++---------- Dockerfile | 16 ++++++++++ Makefile | 31 ------------------- 5 files changed, 44 insertions(+), 58 deletions(-) create mode 100644 Dockerfile delete mode 100644 Makefile diff --git a/.github/docker/docker-bake.hcl b/.github/docker/docker-bake.hcl index 0f158496c..b4343e7ee 100644 --- a/.github/docker/docker-bake.hcl +++ b/.github/docker/docker-bake.hcl @@ -30,13 +30,10 @@ variable "TAG_LATEST" { default = false } -# Alpine base image with digest for reproducible builds -variable "ALPINE_IMAGE" { - default = "alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412" -} + target "cli" { - dockerfile = ".github/docker/cli.Dockerfile" + dockerfile = "Dockerfile" context = "." tags = compact([ IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", @@ -44,9 +41,6 @@ target "cli" { TAG_LATEST ? (IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest") : "", ]) platforms = ["linux/amd64", "linux/arm64"] - args = { - ALPINE_IMAGE = "${ALPINE_IMAGE}" - } labels = { "org.opencontainers.image.title" = "temporal" "org.opencontainers.image.description" = "Temporal CLI" diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 9cc7b646a..632f9b011 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -38,12 +38,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@v4 with: go-version-file: "go.mod" check-latest: true diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index d8a73cba2..b226ee55e 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@v3 with: ref: ${{ github.event.release.tag_name }} @@ -47,22 +47,29 @@ jobs: - name: Get registry configuration if: steps.check_latest.outputs.is_latest == 'true' id: registry - run: | - REPO_OWNER="${{ github.repository_owner }}" - - # Auto-detect registry based on repository owner - if [[ "$REPO_OWNER" == "temporalio" ]]; then - REGISTRY="docker.io" - echo "type=dockerhub" >> $GITHUB_OUTPUT - echo "repo=" >> $GITHUB_OUTPUT - else - REGISTRY="ghcr.io" - echo "type=ghcr" >> $GITHUB_OUTPUT - echo "repo=${REGISTRY}" >> $GITHUB_OUTPUT - fi - - echo "namespace=${REPO_OWNER}" >> $GITHUB_OUTPUT - echo "image=temporal" >> $GITHUB_OUTPUT + uses: actions/github-script@v7 + with: + script: | + const repoOwner = context.repo.owner; + + // Auto-detect registry based on repository owner + let registry, type, repo; + if (repoOwner === 'temporalio') { + registry = 'docker.io'; + type = 'dockerhub'; + repo = ''; + } else { + registry = 'ghcr.io'; + type = 'ghcr'; + repo = registry; + } + + core.setOutput('type', type); + core.setOutput('repo', repo); + core.setOutput('namespace', repoOwner); + core.setOutput('image', 'temporal'); + + console.log(`Registry: ${registry}, Type: ${type}, Namespace: ${repoOwner}`); - name: Log in to GitHub Container Registry if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'ghcr' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..4640c514d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# syntax=docker/dockerfile:1 + +FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 + +# Install CA certificates and create non-root user +RUN apk add --no-cache ca-certificates && \ + adduser -u 1000 -D temporal + +# Copy the appropriate binary for target architecture +ARG TARGETARCH +COPY dist/nix_linux_${TARGETARCH}/temporal /temporal + +# Run as non-root user temporal +USER temporal:temporal + +ENTRYPOINT ["/temporal"] \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 71de3c2d1..000000000 --- a/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -.PHONY: all gen build fmt-imports update-alpine - -all: gen build - -gen: internal/commands.gen.go - -internal/commands.gen.go: internal/commandsgen/commands.yml - go run ./internal/cmd/gen-commands - -build: - go build ./cmd/temporal - -update-alpine: ## Update Alpine base image to latest version and digest (usage: make update-alpine [ALPINE_TAG=3.22]) - @if [ -n "$(ALPINE_TAG)" ]; then \ - LATEST_TAG=$(ALPINE_TAG); \ - else \ - echo "Fetching latest Alpine version from Docker Hub..."; \ - LATEST_TAG=$$(curl -s https://registry.hub.docker.com/v2/repositories/library/alpine/tags\?page_size=100 | \ - jq -r '.results[].name' | grep -E '^3\.[0-9]+$$' | sort -V | tail -1); \ - fi && \ - echo "Alpine version: $$LATEST_TAG" && \ - DIGEST=$$(docker buildx imagetools inspect alpine:$$LATEST_TAG 2>/dev/null | grep "Digest:" | head -1 | awk '{print $$2}') && \ - DIGEST_HASH=$${DIGEST#sha256:} && \ - echo "Digest: sha256:$$DIGEST_HASH" && \ - ALPINE_FULL="alpine:$$LATEST_TAG@sha256:$$DIGEST_HASH" && \ - if sed --version 2>&1 | grep -q GNU; then \ - sed -i "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" .github/docker/docker-bake.hcl; \ - else \ - sed -i '' "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" .github/docker/docker-bake.hcl; \ - fi && \ - echo "Updated .github/docker/docker-bake.hcl with $$ALPINE_FULL" From de9980513f55cfb1c3a6811c852b497d16a508b2 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:58:53 -0600 Subject: [PATCH 03/16] Convert build-and-publish.yml registry logic to JavaScript - Replace bash script with GitHub Script for registry configuration - Keep all existing functionality including fork support logic - Improve maintainability and consistency with update-latest-tag.yml - Use environment variables to pass inputs to JavaScript script --- .github/workflows/build-and-publish.yml | 118 +++++++++++++----------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 632f9b011..32ccf7930 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -108,60 +108,70 @@ jobs: INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} - run: | - echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "image_sha_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - echo "image_branch_tag=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT - - if [[ "$INPUT_PUBLISH" == "true" ]]; then - # Get version from input, strip 'v' prefix - VERSION="$INPUT_VERSION" - VERSION="${VERSION#v}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - else - echo "version=snapshot" >> $GITHUB_OUTPUT - fi - - # Determine registry (with auto-detection for temporalio vs forks) - REGISTRY="$INPUT_REGISTRY" - if [[ -z "$REGISTRY" ]]; then - if [[ "$REPO_OWNER" == "temporalio" ]]; then - REGISTRY="docker.io" - else - REGISTRY="ghcr.io" - fi - fi - - # Determine registry type for authentication - if [[ "$REGISTRY" == "ghcr.io" ]]; then - echo "registry_type=ghcr" >> $GITHUB_OUTPUT - elif [[ "$REGISTRY" == "docker.io" ]]; then - echo "registry_type=dockerhub" >> $GITHUB_OUTPUT - else - echo "registry_type=other" >> $GITHUB_OUTPUT - fi - - # Set namespace (defaults to repository owner) - NAMESPACE="$INPUT_REGISTRY_NAMESPACE" - if [[ -z "$NAMESPACE" ]]; then - NAMESPACE="$REPO_OWNER" - fi - - # Set image name (defaults to 'temporal') - IMAGE_NAME="$INPUT_IMAGE_NAME" - if [[ -z "$IMAGE_NAME" ]]; then - IMAGE_NAME="temporal" - fi - - # For Docker Hub, use empty string as registry (special case) - if [[ "$REGISTRY" == "docker.io" ]]; then - echo "image_repo=" >> $GITHUB_OUTPUT - else - echo "image_repo=${REGISTRY}" >> $GITHUB_OUTPUT - fi - - echo "image_namespace=${NAMESPACE}" >> $GITHUB_OUTPUT - echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT + uses: actions/github-script@v7 + with: + script: | + const inputVersion = process.env.INPUT_VERSION; + const inputPublish = process.env.INPUT_PUBLISH; + const inputRegistry = process.env.INPUT_REGISTRY; + const inputRegistryNamespace = process.env.INPUT_REGISTRY_NAMESPACE; + const inputImageName = process.env.INPUT_IMAGE_NAME; + const repoOwner = process.env.REPO_OWNER; + + // Get git information + const { execSync } = require('child_process'); + const cliSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); + const imageShaTag = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); + const imageBranchTag = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim(); + + core.setOutput('cli_sha', cliSha); + core.setOutput('image_sha_tag', imageShaTag); + core.setOutput('image_branch_tag', imageBranchTag); + + // Determine version + let version; + if (inputPublish === 'true') { + // Get version from input, strip 'v' prefix + version = inputVersion.startsWith('v') ? inputVersion.slice(1) : inputVersion; + } else { + version = 'snapshot'; + } + core.setOutput('version', version); + + // Determine registry (with auto-detection for temporalio vs forks) + let registry = inputRegistry; + if (!registry) { + if (repoOwner === 'temporalio') { + registry = 'docker.io'; + } else { + registry = 'ghcr.io'; + } + } + + // Determine registry type for authentication + let registryType; + if (registry === 'ghcr.io') { + registryType = 'ghcr'; + } else if (registry === 'docker.io') { + registryType = 'dockerhub'; + } else { + registryType = 'other'; + } + core.setOutput('registry_type', registryType); + + // Set namespace (defaults to repository owner) + const namespace = inputRegistryNamespace || repoOwner; + core.setOutput('image_namespace', namespace); + + // Set image name (defaults to 'temporal') + const imageName = inputImageName || 'temporal'; + core.setOutput('image_name', imageName); + + // For Docker Hub, use empty string as registry (special case) + const imageRepo = registry === 'docker.io' ? '' : registry; + core.setOutput('image_repo', imageRepo); + + console.log(`Registry: ${registry}, Type: ${registryType}, Namespace: ${namespace}, Image: ${imageName}`); - name: Log in to GitHub Container Registry if: inputs.publish && steps.meta.outputs.registry_type == 'ghcr' From 3d355a18386aaee974233c1da6d2a069564f54fe Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:00:00 -0600 Subject: [PATCH 04/16] Convert Dockerfile to use distroless base image - Replace Alpine base with gcr.io/distroless/static-debian12:nonroot - Use multi-stage build to copy CA certificates from temporary Alpine stage - Remove user creation since distroless images handle non-root execution - Improve security and reduce image size with distroless approach - Maintain same functionality while following security best practices --- Dockerfile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4640c514d..73878546c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,18 @@ # syntax=docker/dockerfile:1 -FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 +# Build stage to extract CA certificates +FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 AS certs +RUN apk add --no-cache ca-certificates -# Install CA certificates and create non-root user -RUN apk add --no-cache ca-certificates && \ - adduser -u 1000 -D temporal +# Final distroless stage +FROM gcr.io/distroless/static-debian12:nonroot + +# Copy CA certificates from certs stage +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # Copy the appropriate binary for target architecture ARG TARGETARCH COPY dist/nix_linux_${TARGETARCH}/temporal /temporal -# Run as non-root user temporal -USER temporal:temporal - +# Set entrypoint ENTRYPOINT ["/temporal"] \ No newline at end of file From 5f92312285aec4a6c6da7896e3b2443656778237 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:02:44 -0600 Subject: [PATCH 05/16] Move docker-bake.hcl to root and update references - Move docker-bake.hcl from .github/docker/ to repo root - Update workflow references to use new file location - Remove old cli.Dockerfile from .github/docker/ directory - Consolidate Docker build files in root directory for better organization - Maintain all existing functionality with cleaner file structure --- .github/docker/cli.Dockerfile | 34 ------------------- .github/workflows/build-and-publish.yml | 4 +-- .../docker/docker-bake.hcl => docker-bake.hcl | 0 3 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 .github/docker/cli.Dockerfile rename .github/docker/docker-bake.hcl => docker-bake.hcl (100%) diff --git a/.github/docker/cli.Dockerfile b/.github/docker/cli.Dockerfile deleted file mode 100644 index d64700de0..000000000 --- a/.github/docker/cli.Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG ALPINE_IMAGE -ARG BUILDARCH - -# Build stage - copy binaries from goreleaser output -FROM --platform=$BUILDARCH scratch AS dist -COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal -COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal - -# Stage to extract CA certificates and create user files -FROM ${ALPINE_IMAGE} AS certs -RUN apk add --no-cache ca-certificates && \ - adduser -u 1000 -D temporal - -# Final stage - minimal scratch-based image -FROM scratch - -ARG TARGETARCH - -# Copy CA certificates from certs stage -COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ - -# Copy passwd and group files for non-root user -COPY --from=certs /etc/passwd /etc/passwd -COPY --from=certs /etc/group /etc/group - -# Copy the appropriate binary for target architecture -COPY --from=dist /dist/$TARGETARCH/temporal /temporal - -# Run as non-root user temporal (uid 1000) -USER 1000:1000 - -ENTRYPOINT ["/temporal"] diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 32ccf7930..5084d8713 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -192,7 +192,7 @@ jobs: if: inputs.publish run: | docker buildx bake \ - --file .github/docker/docker-bake.hcl \ + --file docker-bake.hcl \ --push \ cli env: @@ -210,7 +210,7 @@ jobs: if: ${{ !inputs.publish }} run: | docker buildx bake \ - --file .github/docker/docker-bake.hcl \ + --file docker-bake.hcl \ cli env: CLI_SHA: ${{ steps.meta.outputs.cli_sha }} diff --git a/.github/docker/docker-bake.hcl b/docker-bake.hcl similarity index 100% rename from .github/docker/docker-bake.hcl rename to docker-bake.hcl From 2d3086e8cecfe6aa41ce4f515a34048a6f3fa736 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:04:26 -0600 Subject: [PATCH 06/16] Convert Dockerfile to use scratch base for maximum minimalism - Replace gcr.io/distroless/static-debian12:nonroot with FROM scratch - Create completely minimal image with only essential components - Manually copy CA certificates, passwd, and group files from Alpine stage - Maintain non-root user execution with USER 1000:1000 - Achieve smallest possible attack surface and image size - Follow container security best practices with truly minimal base --- Dockerfile | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 73878546c..dc4773e0c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,25 @@ # syntax=docker/dockerfile:1 -# Build stage to extract CA certificates +# Build stage to extract CA certificates and create user files FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 AS certs -RUN apk add --no-cache ca-certificates +RUN apk add --no-cache ca-certificates && \ + adduser -u 1000 -D temporal -# Final distroless stage -FROM gcr.io/distroless/static-debian12:nonroot +# Final scratch stage - completely minimal base +FROM scratch # Copy CA certificates from certs stage COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +# Copy passwd and group files for non-root user +COPY --from=certs /etc/passwd /etc/passwd +COPY --from=certs /etc/group /etc/group + # Copy the appropriate binary for target architecture ARG TARGETARCH COPY dist/nix_linux_${TARGETARCH}/temporal /temporal -# Set entrypoint +# Run as non-root user temporal (uid 1000) +USER 1000:1000 + ENTRYPOINT ["/temporal"] \ No newline at end of file From f64d6f2a58348952a062bdfe1094595695202829 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:11:54 -0600 Subject: [PATCH 07/16] Fix Dockerfile to work with GoReleaser output structure - Use ARG TARGETARCH (built-in Docker Buildx arg) instead of custom BUILDARCH - Copy binaries from GoReleaser output structure: dist/nix_linux_amd64_v1/temporal - Add multi-stage dist build to properly handle GoReleaser output paths - Remove unnecessary TARGETPLATFORM from workflows and docker-bake.hcl - Ensure Docker build works correctly with actual GoReleaser binary locations --- .github/workflows/build-and-publish.yml | 1 + Dockerfile | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 5084d8713..94cc27ac7 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -222,3 +222,4 @@ jobs: IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} + TARGETPLATFORM: linux/amd64,linux/arm64 diff --git a/Dockerfile b/Dockerfile index dc4773e0c..012ab1b54 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,13 @@ # syntax=docker/dockerfile:1 -# Build stage to extract CA certificates and create user files +ARG TARGETARCH + +# Build stage - copy binaries from goreleaser output +FROM --platform=$TARGETARCH scratch AS dist +COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal +COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal + +# Stage to extract CA certificates and create user files FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 AS certs RUN apk add --no-cache ca-certificates && \ adduser -u 1000 -D temporal @@ -8,6 +15,8 @@ RUN apk add --no-cache ca-certificates && \ # Final scratch stage - completely minimal base FROM scratch +ARG TARGETARCH + # Copy CA certificates from certs stage COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ @@ -16,10 +25,9 @@ COPY --from=certs /etc/passwd /etc/passwd COPY --from=certs /etc/group /etc/group # Copy the appropriate binary for target architecture -ARG TARGETARCH -COPY dist/nix_linux_${TARGETARCH}/temporal /temporal +COPY --from=dist /dist/$TARGETARCH/temporal /temporal -# Run as non-root user temporal (uid 1000) -USER 1000:1000 +# Run as non-root user temporal +USER temporal:temporal ENTRYPOINT ["/temporal"] \ No newline at end of file From 144d933e9f1d0335334c1aed251ac79480724fba Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:13:16 -0600 Subject: [PATCH 08/16] Clean up Docker build file organization - Remove empty .github/docker directory since all files moved to root - dist/ already exists in .gitignore (no changes needed) - Consolidate all Docker build files in root directory - Final clean structure: Dockerfile, docker-bake.hcl in repo root --- .github/docker/.gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/docker/.gitignore diff --git a/.github/docker/.gitignore b/.github/docker/.gitignore deleted file mode 100644 index 567609b12..000000000 --- a/.github/docker/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ From 312bceffafe93b6e8ba35be885e901aecb64f25d Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:16:40 -0600 Subject: [PATCH 09/16] Add latest tag detection for Docker image builds - Add step to detect if release is marked as latest (not prerelease/draft) - Use GitHub API to check release status via github-script - Set TAG_LATEST based on actual release latest status - Push both version tag and latest tag when appropriate - Maintains existing version tagging behavior for non-latest releases - Works for both temporalio (docker.io) and fork (ghcr.io) registries --- .github/workflows/build-and-publish.yml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 94cc27ac7..1598fccd9 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -69,6 +69,25 @@ jobs: id: go run: echo "go=$(go version | cut -d ' ' -f 3)" >> $GITHUB_OUTPUT + - name: Check if release is latest + if: inputs.publish + id: check_latest_release + uses: actions/github-script@v7 + with: + script: | + const releaseTag = '${{ inputs.version }}'; + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: releaseTag + }); + + const isLatest = !release.prerelease && !release.draft; + core.setOutput('is_latest', isLatest); + console.log(`Release: ${release.tag_name}`); + console.log(`Prerelease: ${release.prerelease}, Draft: ${release.draft}`); + console.log(`Should tag as latest: ${isLatest}`); + - name: Run GoReleaser (release) if: inputs.publish uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 @@ -200,7 +219,7 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: false + TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest }} IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} @@ -217,9 +236,8 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: false + TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest }} IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} - TARGETPLATFORM: linux/amd64,linux/arm64 From 92f17f319a4dc58c228ffb8b9cccec87ed45e866 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:18:46 -0600 Subject: [PATCH 10/16] Fix latest tag logic to push both latest and release tags - Correct TAG_LATEST logic to use boolean comparison == 'true' - When release is latest: push both 'latest' AND 'vX.Y.Z' tags - When release is not latest: push only 'vX.Y.Z' tag (no latest) - Ensures users can always pull :latest for stable releases - Maintains version-specific tags for all releases --- .github/workflows/build-and-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 1598fccd9..dcb78559d 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -219,7 +219,7 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest }} + TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} @@ -236,7 +236,7 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest }} + TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} From 86172ad4956f9eb68f4d65682503dc2397b41a00 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:28:02 -0600 Subject: [PATCH 11/16] Fix Docker build bugs: remove invalid platform ARG and correct TAG_LATEST for snapshots --- .github/workflows/build-and-publish.yml | 2 +- Dockerfile | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index dcb78559d..d8bde874c 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -236,7 +236,7 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} + TAG_LATEST: false IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} diff --git a/Dockerfile b/Dockerfile index 012ab1b54..68f3819a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,7 @@ # syntax=docker/dockerfile:1 -ARG TARGETARCH - # Build stage - copy binaries from goreleaser output -FROM --platform=$TARGETARCH scratch AS dist +FROM scratch AS dist COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal @@ -27,7 +25,7 @@ COPY --from=certs /etc/group /etc/group # Copy the appropriate binary for target architecture COPY --from=dist /dist/$TARGETARCH/temporal /temporal -# Run as non-root user temporal -USER temporal:temporal +# Run as non-root user temporal (uid 1000) +USER 1000:1000 -ENTRYPOINT ["/temporal"] \ No newline at end of file +ENTRYPOINT ["/temporal"] From 3dedd3ea68c877c0217db5505a0a8b1e5d800acc Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:35:53 -0600 Subject: [PATCH 12/16] Use intermediate dist stage for reliable multi-platform builds The intermediate stage approach copies both architecture binaries into normalized paths within Docker's build context, then uses TARGETARCH to select the correct one. This is more reliable than filesystem wildcards which depend on the host build environment. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 68f3819a2..bea772d92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -# Build stage - copy binaries from goreleaser output +# Build stage - copy binaries from goreleaser output and normalize paths FROM scratch AS dist COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal From 942869d4233eae0c43f244523132b75f69e3aacf Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:39:48 -0600 Subject: [PATCH 13/16] Simplify Dockerfile to use Alpine base without intermediate stage Remove the intermediate dist stage and copy binaries directly using TARGETARCH-based wildcards. The Alpine base (digest-pinned to 3.22) provides CA certificates and a non-root user environment in a simple, maintainable format. --- Dockerfile | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index bea772d92..c55f3117b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,10 @@ # syntax=docker/dockerfile:1 -# Build stage - copy binaries from goreleaser output and normalize paths -FROM scratch AS dist -COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal -COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal - -# Stage to extract CA certificates and create user files -FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 AS certs -RUN apk add --no-cache ca-certificates && \ - adduser -u 1000 -D temporal - -# Final scratch stage - completely minimal base -FROM scratch - +FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 ARG TARGETARCH +RUN apk add --no-cache ca-certificates +COPY ./dist/nix_linux_${TARGETARCH}_*/temporal /usr/local/bin/temporal +RUN adduser -u 1000 -D temporal +USER temporal -# Copy CA certificates from certs stage -COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ - -# Copy passwd and group files for non-root user -COPY --from=certs /etc/passwd /etc/passwd -COPY --from=certs /etc/group /etc/group - -# Copy the appropriate binary for target architecture -COPY --from=dist /dist/$TARGETARCH/temporal /temporal - -# Run as non-root user temporal (uid 1000) -USER 1000:1000 - -ENTRYPOINT ["/temporal"] +ENTRYPOINT ["temporal"] From 1db66a61dee7032818b427043d0ed3a95fe35069 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:43:47 -0600 Subject: [PATCH 14/16] Clean up Dockerfile: remove syntax comment and simplify structure - Remove syntax directive comment - Remove leading ./ from COPY path for consistency - ARG TARGETARCH declared after FROM (required for build stage scope) - Verified working with goreleaser output structure --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index c55f3117b..11673c0c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -# syntax=docker/dockerfile:1 - FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 + ARG TARGETARCH + RUN apk add --no-cache ca-certificates -COPY ./dist/nix_linux_${TARGETARCH}_*/temporal /usr/local/bin/temporal +COPY dist/nix_linux_${TARGETARCH}_*/temporal /usr/local/bin/temporal RUN adduser -u 1000 -D temporal USER temporal From ff9df1aceb4c8c73a289e32c97326d0421da90e7 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:45:28 -0600 Subject: [PATCH 15/16] Add explicit chmod to COPY instruction for binary permissions --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 11673c0c8..9dc638da2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2 ARG TARGETARCH RUN apk add --no-cache ca-certificates -COPY dist/nix_linux_${TARGETARCH}_*/temporal /usr/local/bin/temporal +COPY --chmod=755 dist/nix_linux_${TARGETARCH}_*/temporal /usr/local/bin/temporal RUN adduser -u 1000 -D temporal USER temporal From 21f02c621021efe85d43100649639f4a8adbc080 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:47:18 -0600 Subject: [PATCH 16/16] Restore intermediate dist stage for explicit path normalization The intermediate stage approach is more reliable than wildcards as it: - Operates entirely within Docker's build context - Explicitly copies both architecture binaries to normalized paths - Allows clean selection via TARGETARCH without filesystem assumptions - Avoids reliance on shell glob expansion behavior --- Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9dc638da2..23de4ed74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,16 @@ +# Intermediate stage to normalize goreleaser output paths +# This copies both architecture binaries and renames them to clean paths, +# allowing the final stage to select the correct binary using TARGETARCH +FROM scratch AS dist +COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal +COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal + FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 ARG TARGETARCH RUN apk add --no-cache ca-certificates -COPY --chmod=755 dist/nix_linux_${TARGETARCH}_*/temporal /usr/local/bin/temporal +COPY --chmod=755 --from=dist /dist/${TARGETARCH}/temporal /usr/local/bin/temporal RUN adduser -u 1000 -D temporal USER temporal