diff --git a/.docker/.cli-version b/.docker/.cli-version new file mode 100644 index 00000000000..2e7bd91085b --- /dev/null +++ b/.docker/.cli-version @@ -0,0 +1 @@ +v1.5.0 diff --git a/.docker/.gitignore b/.docker/.gitignore new file mode 100644 index 00000000000..4c65c3ca478 --- /dev/null +++ b/.docker/.gitignore @@ -0,0 +1,5 @@ +# Build artifacts +dist/ + +# Docker compose logs +docker-compose.log diff --git a/.docker/Makefile b/.docker/Makefile new file mode 100644 index 00000000000..9c8f5a3bb8e --- /dev/null +++ b/.docker/Makefile @@ -0,0 +1,100 @@ +CLI_VERSION := $(shell cat .cli-version) +TEMPORAL_SHA := $(shell cd .. && git rev-parse HEAD) +IMAGE_SHA_TAG := sha-$(shell cd .. && git rev-parse --short HEAD) +IMAGE_BRANCH_TAG := branch-$(shell cd .. && git rev-parse --abbrev-ref HEAD) +IMAGE_REPO ?= ghcr.io/chaptersix + +DOCKER := docker buildx +BAKE := cd .. && IMAGE_SHA_TAG=$(IMAGE_SHA_TAG) \ + IMAGE_BRANCH_TAG=$(IMAGE_BRANCH_TAG) \ + TEMPORAL_SHA=$(TEMPORAL_SHA) \ + IMAGE_REPO=$(IMAGE_REPO) \ + $(DOCKER) bake -f .docker/docker-bake.hcl +NATIVE_ARCH := $(shell go env GOARCH) +BAKE_OUTPUT ?= docker + +.PHONY: all +all: prepare-bins build-native + +.PHONY: prepare-bins +prepare-bins: goreleaser-bins download-cli organize-bins + +.PHONY: goreleaser-bins +goreleaser-bins: + @echo "Building binaries with goreleaser..." + @cd .. && goreleaser build --snapshot --clean \ + --id temporal-server \ + --id temporal-cassandra-tool \ + --id temporal-sql-tool \ + --id temporal-elasticsearch-tool \ + --id tdbg + +.PHONY: download-cli +download-cli: + @echo "Downloading temporal CLI ${CLI_VERSION}..." + @mkdir -p dist/tmp + @for arch in amd64 arm64; do \ + echo "Downloading for linux/$$arch..."; \ + VERSION=$$(echo ${CLI_VERSION} | sed 's/^v//'); \ + curl -fsSL "https://github.com/temporalio/cli/releases/download/${CLI_VERSION}/temporal_cli_$${VERSION}_linux_$$arch.tar.gz" | \ + tar -xzf - -C dist/tmp && \ + mkdir -p dist/$$arch && \ + mv dist/tmp/temporal dist/$$arch/temporal; \ + done + @rm -rf dist/tmp + +.PHONY: organize-bins +organize-bins: + @echo "Organizing binaries for Docker..." + @mkdir -p dist/amd64 dist/arm64 + @echo "Copying amd64 binaries..." + @for binary in temporal-server temporal-cassandra-tool temporal-sql-tool temporal-elasticsearch-tool tdbg; do \ + find ../dist -type f -name "$$binary" | grep linux_amd64 | xargs -I {} cp -v {} dist/amd64/ ; \ + done + @echo "Copying arm64 binaries..." + @for binary in temporal-server temporal-cassandra-tool temporal-sql-tool temporal-elasticsearch-tool tdbg; do \ + find ../dist -type f -name "$$binary" | grep linux_arm64 | xargs -I {} cp -v {} dist/arm64/ ; \ + done + @echo "Verifying binaries..." + @ls -lh dist/amd64/ || echo "Warning: No amd64 binaries found" + @ls -lh dist/arm64/ || echo "Warning: No arm64 binaries found" + +.PHONY: build-native +build-native: prepare-bins + @echo "Building native Docker images..." + $(BAKE) --set "*.platform=linux/$(NATIVE_ARCH)" --load + +.PHONY: build +build: prepare-bins + @echo "Building multi-arch Docker images..." + $(BAKE) --set="*.output=type=$(BAKE_OUTPUT)" + +.PHONY: test +test: + @echo "Testing Docker images..." + IMAGE_SHA_TAG=$(IMAGE_SHA_TAG) IMAGE_REPO=$(IMAGE_REPO) ./scripts/test.sh + +.PHONY: clean +clean: + @echo "Cleaning build artifacts..." + rm -rf dist/ + rm -rf ../dist/ + +.PHONY: help +help: + @echo "Temporal Docker Build Makefile" + @echo "" + @echo "Targets:" + @echo " all - Build binaries and native Docker images (default)" + @echo " prepare-bins - Build binaries with goreleaser and download CLI" + @echo " goreleaser-bins - Build Temporal binaries with goreleaser" + @echo " download-cli - Download Temporal CLI from GitHub releases" + @echo " organize-bins - Organize binaries for Docker build" + @echo " build-native - Build Docker images for native architecture" + @echo " build - Build multi-arch Docker images" + @echo " test - Test Docker images" + @echo " clean - Clean build artifacts" + @echo "" + @echo "Environment Variables:" + @echo " IMAGE_REPO - Docker image repository (default: ghcr.io/chaptersix)" + @echo " BAKE_OUTPUT - Docker output type: docker or registry (default: docker)" diff --git a/.docker/README.md b/.docker/README.md new file mode 100644 index 00000000000..446843545bb --- /dev/null +++ b/.docker/README.md @@ -0,0 +1,218 @@ +# Temporal Docker Build System + +This directory contains everything needed to build Temporal's Docker images using goreleaser and GHCR. + +## Overview + +The build system creates two main Docker images: +- **server**: Contains temporal-server and temporal CLI +- **admin-tools**: Contains temporal CLI, tdbg, and database tools (cassandra-tool, sql-tool, elasticsearch-tool) + +## Local Development + +### Prerequisites +- Docker with buildx support +- Go 1.21+ +- goreleaser (`brew install --cask goreleaser`) +- make + +### Building Images Locally + +```bash +cd .docker + +# Build native architecture images (fast) +make build-native + +# Build multi-arch images (amd64 + arm64) +make build + +# Test images +make test + +# Clean build artifacts +make clean +``` + +### Manual Build Steps + +```bash +# 1. Build binaries with goreleaser +make goreleaser-bins + +# 2. Download temporal CLI from GitHub releases +make download-cli + +# 3. Organize binaries for Docker +make organize-bins + +# 4. Build Docker images +make build-native +``` + +## CI/CD Workflows + +### Automatic Builds + +The `.github/workflows/docker-build.yml` workflow automatically builds and pushes images to GHCR on: +- Push to `main` branch +- Push to `test/*`, `cloud/*`, `feature/*`, `release/*` branches +- Pull requests (build only, no push) + +**Images are tagged with:** +- `sha-` (e.g., `sha-abc1234`) +- `branch-` (e.g., `branch-main`) +- `latest` (only for main branch) + +### Manual Releases + +Use `.github/workflows/release-docker-images.yml` to promote images to production: + +```bash +# Promote a pre-built image to release registry +# Input: commit SHA (first 7 chars), version tag, flags for latest/major +``` + +### Base Image Updates + +Use `.github/workflows/release-base-images.yml` to build new base images when dependencies change. + +## Directory Structure + +``` +.docker/ +├── base-images/ # Base image Dockerfiles +│ ├── base-server.Dockerfile +│ └── base-admin-tools.Dockerfile +├── server.Dockerfile # Server image +├── admin-tools.Dockerfile # Admin tools image +├── docker-bake.hcl # Docker buildx bake configuration +├── scripts/ # Runtime scripts +│ ├── entrypoint.sh +│ ├── start-temporal.sh +│ └── test.sh +├── dist/ # Build output (gitignored) +│ ├── amd64/ # Linux amd64 binaries +│ └── arm64/ # Linux arm64 binaries +├── .cli-version # Temporal CLI version to download +├── docker-compose.yml # Testing compose file +├── Makefile # Build automation +└── README.md # This file +``` + +## Configuration + +### Temporal CLI Version + +Update `.cli-version` to change which temporal CLI version is downloaded: + +```bash +echo "v1.6.0" > .cli-version +``` + +### Image Registry + +The default registry is `ghcr.io/chaptersix` for testing. To change: + +**In docker-bake.hcl:** +```hcl +variable "IMAGE_REPO" { + default = "ghcr.io/your-org" # or "dockerhub-username" +} +``` + +**In workflows:** +Update the `IMAGE_REPO` environment variable or registry login steps. + +## Testing + +### Local Testing + +```bash +# Build and test +make build-native test + +# Run specific tests +docker run --rm ghcr.io/chaptersix/server:sha-test temporal-server --version +docker run --rm ghcr.io/chaptersix/admin-tools:sha-test temporal --version +``` + +### Integration Testing + +Use docker-compose for integration tests: + +```bash +IMAGE_SHA_TAG=sha-test docker compose up -d +docker compose exec admin-tools temporal operator cluster health +docker compose down +``` + +## Goreleaser Configuration + +The build uses `../.goreleaser.yml` to build temporal binaries: +- temporal-server +- temporal-cassandra-tool +- temporal-sql-tool +- temporal-elasticsearch-tool +- tdbg + +**Note:** The goreleaser config was updated to v2 format. Key changes: +- Added `version: 2` at the top +- Changed `changelog.skip` to `changelog.disable` + +## Troubleshooting + +### Goreleaser build fails +```bash +# Check goreleaser version +goreleaser --version + +# Validate config +goreleaser check +``` + +### Binaries not found in Docker build +```bash +# Check organized binaries +ls -la dist/amd64/ dist/arm64/ + +# Manually run organize-bins +make organize-bins +``` + +### CLI download fails +```bash +# Check version in .cli-version +cat .cli-version + +# Verify release exists +curl -I https://github.com/temporalio/cli/releases/download/$(cat .cli-version)/temporal_cli_$(cat .cli-version | sed 's/^v//')_linux_amd64.tar.gz +``` + +### Docker build context errors +The Dockerfiles expect to be run from the temporal repo root, not from `.docker/`. +The Makefile handles this automatically with `cd ..`. + +## Migration Notes + +This docker build system replaces the docker-builds repository approach: + +**Old approach:** +- Separate docker-builds repo with submodules +- Built CLI and tctl from source +- Cross-repo workflow triggers + +**New approach:** +- Everything in temporal repo +- Downloads CLI from releases +- No tctl (deprecated) +- Uses goreleaser for all temporal binaries +- Simpler, single-repo workflow + +## Contributing + +When making changes: +1. Test locally with `make build-native test` +2. Verify goreleaser config still works +3. Update this README if adding new features +4. Check that both amd64 and arm64 builds work diff --git a/.docker/admin-tools.Dockerfile b/.docker/admin-tools.Dockerfile new file mode 100644 index 00000000000..275f4241105 --- /dev/null +++ b/.docker/admin-tools.Dockerfile @@ -0,0 +1,23 @@ +ARG BASE_ADMIN_TOOLS_IMAGE=temporalio/base-admin-tools:1.12.12 + +FROM ${BASE_ADMIN_TOOLS_IMAGE} as temporal-admin-tools +ARG TARGETARCH + +# Essential binaries only +COPY ./.docker/dist/${TARGETARCH}/temporal /usr/local/bin/ +COPY ./.docker/dist/${TARGETARCH}/tdbg /usr/local/bin/ +COPY ./.docker/dist/${TARGETARCH}/temporal-cassandra-tool /usr/local/bin/ +COPY ./.docker/dist/${TARGETARCH}/temporal-sql-tool /usr/local/bin/ +COPY ./.docker/dist/${TARGETARCH}/temporal-elasticsearch-tool /usr/local/bin/ +COPY ./schema /etc/temporal/schema + +# Setup bash completion +RUN apk add bash-completion && \ + temporal completion bash > /etc/bash/temporal-completion.sh && \ + addgroup -g 1000 temporal && \ + adduser -u 1000 -G temporal -D temporal + +USER temporal +WORKDIR /etc/temporal + +ENTRYPOINT ["tini", "--", "sleep", "infinity"] diff --git a/.docker/base-images/base-admin-tools.Dockerfile b/.docker/base-images/base-admin-tools.Dockerfile new file mode 100644 index 00000000000..62dc9660990 --- /dev/null +++ b/.docker/base-images/base-admin-tools.Dockerfile @@ -0,0 +1,38 @@ +ARG BASE_IMAGE=alpine:3.22 + +FROM ${BASE_IMAGE} AS builder + +# These are necessary to install cqlsh +RUN apk add --update --no-cache \ + python3-dev \ + musl-dev \ + libev-dev \ + gcc \ + pipx + +RUN pipx install --global cqlsh + +FROM ${BASE_IMAGE} AS base-admin-tools + +RUN apk upgrade --no-cache +RUN apk add --no-cache \ + python3 \ + libev \ + ca-certificates \ + tzdata \ + bash \ + curl \ + jq \ + yq \ + mysql-client \ + postgresql-client \ + expat \ + tini + +COPY --from=builder /opt/pipx/venvs/cqlsh /opt/pipx/venvs/cqlsh +RUN ln -s /opt/pipx/venvs/cqlsh/bin/cqlsh /usr/local/bin/cqlsh + +# validate cqlsh installation +RUN cqlsh --version + +SHELL ["/bin/bash", "-c"] diff --git a/.docker/base-images/base-server.Dockerfile b/.docker/base-images/base-server.Dockerfile new file mode 100644 index 00000000000..c806dba8ff4 --- /dev/null +++ b/.docker/base-images/base-server.Dockerfile @@ -0,0 +1,21 @@ +ARG BASE_IMAGE=alpine:3.22 + +FROM golang:1.24-alpine3.22 AS builder + +ARG DOCKERIZE_VERSION=v0.9.2 +RUN go install github.com/jwilder/dockerize@${DOCKERIZE_VERSION} +RUN cp $(which dockerize) /usr/local/bin/dockerize + +##### base-server target ##### +FROM ${BASE_IMAGE} AS base-server + +RUN apk upgrade --no-cache +RUN apk add --no-cache \ + ca-certificates \ + tzdata \ + bash \ + curl + +COPY --from=builder /usr/local/bin/dockerize /usr/local/bin + +SHELL ["/bin/bash", "-c"] diff --git a/.docker/docker-bake.hcl b/.docker/docker-bake.hcl new file mode 100644 index 00000000000..20912e223cd --- /dev/null +++ b/.docker/docker-bake.hcl @@ -0,0 +1,67 @@ +variable "platforms" { + default = ["linux/amd64", "linux/arm64"] +} + +variable "IMAGE_REPO" { + default = "ghcr.io/chaptersix" +} + +variable "IMAGE_SHA_TAG" {} + +variable "IMAGE_BRANCH_TAG" {} + +variable "SAFE_IMAGE_BRANCH_TAG" { + default = join("-", [for c in regexall("[a-z0-9]+", lower(IMAGE_BRANCH_TAG)) : c]) +} + +variable "TEMPORAL_SHA" {} + +variable "TAG_LATEST" { + default = false +} + +group "default" { + targets = [ + "server", + "admin-tools", + ] +} + +target "server" { + dockerfile = ".docker/server.Dockerfile" + context = "." + tags = [ + "${IMAGE_REPO}/server:${IMAGE_SHA_TAG}", + "${IMAGE_REPO}/server:${SAFE_IMAGE_BRANCH_TAG}", + TAG_LATEST ? "${IMAGE_REPO}/server:latest" : "" + ] + platforms = platforms + args = { + TEMPORAL_SHA = "${TEMPORAL_SHA}" + } + labels = { + "org.opencontainers.image.title" = "server" + "org.opencontainers.image.description" = "Temporal Server" + "org.opencontainers.image.url" = "https://github.com/temporalio/temporal" + "org.opencontainers.image.source" = "https://github.com/temporalio/temporal" + "org.opencontainers.image.licenses" = "MIT" + } +} + +target "admin-tools" { + dockerfile = ".docker/admin-tools.Dockerfile" + context = "." + tags = [ + "${IMAGE_REPO}/admin-tools:${IMAGE_SHA_TAG}", + "${IMAGE_REPO}/admin-tools:${SAFE_IMAGE_BRANCH_TAG}", + TAG_LATEST ? "${IMAGE_REPO}/admin-tools:latest" : "" + ] + platforms = platforms + labels = { + "org.opencontainers.image.title" = "admin-tools" + "org.opencontainers.image.description" = "Temporal Admin Tools" + "org.opencontainers.image.url" = "https://github.com/temporalio/temporal" + "org.opencontainers.image.source" = "https://github.com/temporalio/temporal" + "org.opencontainers.image.licenses" = "MIT" + } +} diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml new file mode 100644 index 00000000000..ebf7e1ef254 --- /dev/null +++ b/.docker/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.5" + +services: + server: + image: ${IMAGE_REPO:-ghcr.io/chaptersix}/server:${IMAGE_SHA_TAG:-sha-test} + ports: + - "7233:7233" + environment: + - DB=sqlite + volumes: + - /tmp/temporal:/tmp/temporal + + admin-tools: + image: ${IMAGE_REPO:-ghcr.io/chaptersix}/admin-tools:${IMAGE_SHA_TAG:-sha-test} + depends_on: + - server + environment: + - TEMPORAL_ADDRESS=server:7233 diff --git a/.docker/scripts/entrypoint.sh b/.docker/scripts/entrypoint.sh new file mode 100755 index 00000000000..25a33827258 --- /dev/null +++ b/.docker/scripts/entrypoint.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -eu -o pipefail + +: "${BIND_ON_IP:=$(getent hosts "$(hostname)" | awk '{print $1;}')}" +export BIND_ON_IP + +if [[ "${BIND_ON_IP}" == "0.0.0.0" || "${BIND_ON_IP}" == "::0" ]]; then + : "${TEMPORAL_BROADCAST_ADDRESS:=$(getent hosts "$(hostname)" | awk '{print $1;}')}" + export TEMPORAL_BROADCAST_ADDRESS +fi + +# check TEMPORAL_ADDRESS is not empty +if [[ -z "${TEMPORAL_ADDRESS:-}" ]]; then + echo "TEMPORAL_ADDRESS is not set, setting it to ${BIND_ON_IP}:7233" + + if [[ "${BIND_ON_IP}" =~ ":" ]]; then + # ipv6 + export TEMPORAL_ADDRESS="[${BIND_ON_IP}]:7233" + else + # ipv4 + export TEMPORAL_ADDRESS="${BIND_ON_IP}:7233" + fi +fi + +# Support TEMPORAL_CLI_ADDRESS for backwards compatibility. +# TEMPORAL_CLI_ADDRESS is deprecated and support for it will be removed in the future release. +if [[ -z "${TEMPORAL_CLI_ADDRESS:-}" ]]; then + export TEMPORAL_CLI_ADDRESS="${TEMPORAL_ADDRESS}" +fi + +dockerize -template /etc/temporal/config/config_template.yaml:/etc/temporal/config/docker.yaml + +# Automatically setup Temporal Server (databases, Elasticsearch, default namespace) if "autosetup" is passed as an argument. +for arg; do + if [[ ${arg} == autosetup ]]; then + /etc/temporal/auto-setup.sh + break + fi +done + +# Setup Temporal Server in development mode if "develop" is passed as an argument. +for arg; do + if [[ ${arg} == develop ]]; then + /etc/temporal/setup-develop.sh + break + fi +done + +# Run bash instead of Temporal Server if "bash" is passed as an argument (convenient to debug docker image). +for arg; do + if [[ ${arg} == bash ]]; then + bash + exit 0 + fi +done + +exec /etc/temporal/start-temporal.sh diff --git a/.docker/scripts/start-temporal.sh b/.docker/scripts/start-temporal.sh new file mode 100755 index 00000000000..ba36b4cefbf --- /dev/null +++ b/.docker/scripts/start-temporal.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -eu -o pipefail + +: "${SERVICES:=}" + +flags=() +if [[ -n ${SERVICES} ]]; then + # Convert colon (or comma, for backward compatibility) separated string (i.e. "history:matching") + # to valid flag list (i.e. "--service=history --service=matching"). + SERVICES="${SERVICES//:/,}" + SERVICES="${SERVICES//,/ }" + for i in $SERVICES; do flags+=("--service=$i"); done +fi + +exec temporal-server --env docker start "${flags[@]}" diff --git a/.docker/scripts/test.sh b/.docker/scripts/test.sh new file mode 100755 index 00000000000..166cbc1c29f --- /dev/null +++ b/.docker/scripts/test.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -euo pipefail + +IMAGE_SHA_TAG="${IMAGE_SHA_TAG:-sha-test}" +IMAGE_REPO="${IMAGE_REPO:-ghcr.io/chaptersix}" + +echo "Testing images with tag: ${IMAGE_SHA_TAG}" + +# Test server image +echo "Testing server image..." +docker run --rm "${IMAGE_REPO}/server:${IMAGE_SHA_TAG}" temporal-server --version + +# Test admin-tools image +echo "Testing admin-tools image..." +docker run --rm "${IMAGE_REPO}/admin-tools:${IMAGE_SHA_TAG}" temporal --version +docker run --rm "${IMAGE_REPO}/admin-tools:${IMAGE_SHA_TAG}" tdbg --version +docker run --rm "${IMAGE_REPO}/admin-tools:${IMAGE_SHA_TAG}" temporal-cassandra-tool --version +docker run --rm "${IMAGE_REPO}/admin-tools:${IMAGE_SHA_TAG}" temporal-sql-tool --version + +echo "✅ All image tests passed!" diff --git a/.docker/server.Dockerfile b/.docker/server.Dockerfile new file mode 100644 index 00000000000..c5fb5e55629 --- /dev/null +++ b/.docker/server.Dockerfile @@ -0,0 +1,33 @@ +ARG BASE_SERVER_IMAGE=temporalio/base-server:1.15.12 + +FROM ${BASE_SERVER_IMAGE} as temporal-server +ARG TARGETARCH +ARG TEMPORAL_SHA=unknown + +WORKDIR /etc/temporal + +ENV TEMPORAL_HOME=/etc/temporal +EXPOSE 6933 6934 6935 6939 7233 7234 7235 7239 + +RUN addgroup -g 1000 temporal && \ + adduser -u 1000 -G temporal -D temporal && \ + mkdir /etc/temporal/config && \ + chown -R temporal:temporal /etc/temporal/config + +USER temporal + +ENV TEMPORAL_SHA=${TEMPORAL_SHA} + +# Binaries from goreleaser + CLI download +COPY ./.docker/dist/${TARGETARCH}/temporal-server /usr/local/bin/ +COPY ./.docker/dist/${TARGETARCH}/temporal /usr/local/bin/ + +# Configs +COPY ./config/dynamicconfig/docker.yaml /etc/temporal/config/dynamicconfig/docker.yaml +COPY ./docker/config_template.yaml /etc/temporal/config/config_template.yaml + +# Scripts +COPY ./.docker/scripts/entrypoint.sh /etc/temporal/entrypoint.sh +COPY ./.docker/scripts/start-temporal.sh /etc/temporal/start-temporal.sh + +ENTRYPOINT ["/etc/temporal/entrypoint.sh"] diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 00000000000..587ee9644a8 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,184 @@ +name: Build Docker Images + +permissions: + contents: read + packages: write + security-events: write + +on: + push: + branches: + - main + - test/* + - cloud/* + - feature/* + - release/* + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + build-push-images: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - 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 + if: github.event_name == 'push' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set environment variables + run: | + echo "IMAGE_SHA_TAG=sha-${GITHUB_SHA:0:7}" >> $GITHUB_ENV + branch_name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + echo "IMAGE_BRANCH_TAG=branch-${branch_name}" >> $GITHUB_ENV + echo "TEMPORAL_SHA=${GITHUB_SHA}" >> $GITHUB_ENV + TAG_LATEST=${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') && 'true' || 'false' }} + echo "TAG_LATEST=${TAG_LATEST}" >> $GITHUB_ENV + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: true + + - name: Install goreleaser + uses: goreleaser/goreleaser-action@v5 + with: + install-only: true + version: latest + + - name: Build binaries with goreleaser + run: | + goreleaser build --snapshot --clean \ + --id temporal-server \ + --id temporal-cassandra-tool \ + --id temporal-sql-tool \ + --id temporal-elasticsearch-tool \ + --id tdbg + + - name: Show goreleaser output structure + run: | + echo "=== Goreleaser dist structure ===" + find dist -type f -name "temporal-server" -o -name "tdbg" -o -name "temporal-*-tool" | sort + + - name: Download temporal CLI + working-directory: .docker + run: | + CLI_VERSION=$(cat .cli-version) + echo "Downloading temporal CLI ${CLI_VERSION}..." + mkdir -p dist/tmp + for arch in amd64 arm64; do + echo "Downloading for linux/${arch}..." + curl -fsSL "https://github.com/temporalio/cli/releases/download/${CLI_VERSION}/temporal_cli_${CLI_VERSION#v}_linux_${arch}.tar.gz" | \ + tar -xzf - -C dist/tmp + mkdir -p dist/${arch} + mv dist/tmp/temporal dist/${arch}/temporal + done + rm -rf dist/tmp + + - name: Organize binaries + run: | + echo "=== Organizing binaries ===" + mkdir -p .docker/dist/amd64 .docker/dist/arm64 + + # Find and copy binaries (goreleaser path varies by version) + find dist -type f -name temporal-server -path "*/linux_amd64*" -exec cp {} .docker/dist/amd64/ \; + find dist -type f -name temporal-cassandra-tool -path "*/linux_amd64*" -exec cp {} .docker/dist/amd64/ \; + find dist -type f -name temporal-sql-tool -path "*/linux_amd64*" -exec cp {} .docker/dist/amd64/ \; + find dist -type f -name temporal-elasticsearch-tool -path "*/linux_amd64*" -exec cp {} .docker/dist/amd64/ \; + find dist -type f -name tdbg -path "*/linux_amd64*" -exec cp {} .docker/dist/amd64/ \; + + find dist -type f -name temporal-server -path "*/linux_arm64*" -exec cp {} .docker/dist/arm64/ \; + find dist -type f -name temporal-cassandra-tool -path "*/linux_arm64*" -exec cp {} .docker/dist/arm64/ \; + find dist -type f -name temporal-sql-tool -path "*/linux_arm64*" -exec cp {} .docker/dist/arm64/ \; + find dist -type f -name temporal-elasticsearch-tool -path "*/linux_arm64*" -exec cp {} .docker/dist/arm64/ \; + find dist -type f -name tdbg -path "*/linux_arm64*" -exec cp {} .docker/dist/arm64/ \; + + echo "=== Verifying binaries ===" + echo "AMD64 binaries:" + ls -lh .docker/dist/amd64/ + echo "ARM64 binaries:" + ls -lh .docker/dist/arm64/ + + - name: Bake native images for testing + working-directory: .docker + run: | + IMAGE_REPO=ghcr.io/chaptersix \ + IMAGE_SHA_TAG=${{ env.IMAGE_SHA_TAG }} \ + IMAGE_BRANCH_TAG=${{ env.IMAGE_BRANCH_TAG }} \ + TEMPORAL_SHA=${{ env.TEMPORAL_SHA }} \ + docker buildx bake -f docker-bake.hcl \ + --set "*.platform=linux/$(go env GOARCH)" \ + --load + + - name: Test images locally + working-directory: .docker + run: | + echo "Testing server image..." + docker run --rm ghcr.io/chaptersix/server:${{ env.IMAGE_SHA_TAG }} temporal-server --version + echo "Testing admin-tools image..." + docker run --rm ghcr.io/chaptersix/admin-tools:${{ env.IMAGE_SHA_TAG }} temporal --version + + - name: Bake and push multiarch images + if: github.event_name == 'push' + working-directory: .docker + run: | + IMAGE_REPO=ghcr.io/chaptersix \ + IMAGE_SHA_TAG=${{ env.IMAGE_SHA_TAG }} \ + IMAGE_BRANCH_TAG=${{ env.IMAGE_BRANCH_TAG }} \ + TEMPORAL_SHA=${{ env.TEMPORAL_SHA }} \ + TAG_LATEST=${{ env.TAG_LATEST }} \ + docker buildx bake -f docker-bake.hcl \ + --set="*.output=type=registry" + + - name: Run Trivy on Server image + if: github.event_name == 'push' + uses: aquasecurity/trivy-action@master + with: + image-ref: ghcr.io/chaptersix/server:${{ env.IMAGE_SHA_TAG }} + format: 'sarif' + output: 'trivy-server.sarif' + + - name: Run Trivy on Admin Tools image + if: github.event_name == 'push' + uses: aquasecurity/trivy-action@master + with: + image-ref: ghcr.io/chaptersix/admin-tools:${{ env.IMAGE_SHA_TAG }} + format: 'sarif' + output: 'trivy-admin-tools.sarif' + + - name: Upload Trivy results to GitHub Security + if: github.event_name == 'push' + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: '.' + + - name: Image summary + if: github.event_name == 'push' + run: | + echo "### 🐳 Published Images" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- \`ghcr.io/chaptersix/server:${{ env.IMAGE_SHA_TAG }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`ghcr.io/chaptersix/admin-tools:${{ env.IMAGE_SHA_TAG }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Pull with:" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "docker pull ghcr.io/chaptersix/server:${{ env.IMAGE_SHA_TAG }}" >> $GITHUB_STEP_SUMMARY + echo "docker pull ghcr.io/chaptersix/admin-tools:${{ env.IMAGE_SHA_TAG }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-base-images.yml b/.github/workflows/release-base-images.yml new file mode 100644 index 00000000000..8cd80acfecb --- /dev/null +++ b/.github/workflows/release-base-images.yml @@ -0,0 +1,76 @@ +name: Release Base Docker Images + +permissions: read-all + +on: + workflow_dispatch: + inputs: + target: + description: "Target base image" + type: choice + options: + - base-server + - base-admin-tools + required: true + tag: + description: "New tag for the image" + required: true + arch: + description: "Platform architecture" + type: choice + options: + - linux/amd64,linux/arm64 + - linux/amd64 + - linux/arm64 + default: linux/amd64,linux/arm64 + registry: + description: "Target registry" + default: "ghcr.io/chaptersix" + +jobs: + build-push-base-image: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - 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 + if: startsWith(github.event.inputs.registry, 'ghcr.io') + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub + if: "!startsWith(github.event.inputs.registry, 'ghcr.io')" + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PAT }} + + - name: Build and push base image + uses: docker/build-push-action@v5 + with: + push: true + pull: true + file: .docker/base-images/${{ github.event.inputs.target }}.Dockerfile + platforms: ${{ github.event.inputs.arch }} + tags: ${{ github.event.inputs.registry }}/${{ github.event.inputs.target }}:${{ github.event.inputs.tag }} + labels: | + org.opencontainers.image.title=${{ github.event.inputs.target }} + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.version=${{ github.event.inputs.tag }} + + - name: Summary + run: | + echo "### ✅ Base Image Released" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Image: \`${{ github.event.inputs.registry }}/${{ github.event.inputs.target }}:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY + echo "Platforms: \`${{ github.event.inputs.arch }}\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-docker-images.yml b/.github/workflows/release-docker-images.yml new file mode 100644 index 00000000000..fcc108fbfcb --- /dev/null +++ b/.github/workflows/release-docker-images.yml @@ -0,0 +1,90 @@ +name: Release Docker Images + +permissions: read-all + +on: + workflow_dispatch: + inputs: + commit: + description: "Commit sha (first 7 chars, e.g., 'abc1234' from sha-abc1234)" + required: true + tag: + description: "Release version tag (e.g., 1.23.4)" + required: true + latest: + type: boolean + description: "Also update latest tag" + default: false + major: + type: boolean + description: "Also update major version tag" + default: false + source_registry: + description: "Source registry" + default: "ghcr.io/chaptersix" + dest_registry: + description: "Destination registry (e.g., temporalio for DockerHub, ghcr.io/temporalio for GHCR)" + default: "ghcr.io/chaptersix" + +jobs: + release-images: + name: "Promote images to release registry" + runs-on: ubuntu-latest + steps: + - name: Promote server and admin-tools images + env: + COMMIT: ${{ github.event.inputs.commit }} + TAG: ${{ github.event.inputs.tag }} + LATEST: ${{ github.event.inputs.latest }} + MAJOR: ${{ github.event.inputs.major }} + SRC_REGISTRY: ${{ github.event.inputs.source_registry }} + DST_REGISTRY: ${{ github.event.inputs.dest_registry }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PAT }} + run: | + # Use skopeo to copy images + for image in server admin-tools; do + echo "Processing ${image}..." + src="${SRC_REGISTRY}/${image}:sha-${COMMIT}" + + # Parse version for tagging + IFS='.' read -ra VERSION <<< "$TAG" + + # Build tag list + tags="${TAG}" + + # Add major tag if requested + if [ "$MAJOR" = "true" ]; then + tags="${VERSION[0]} ${tags}" + fi + + # Add progressive version tags (1, 1.23, 1.23.4) + for i in $(seq 1 $((${#VERSION[@]}-1))); do + partial_version=$(IFS=.; echo "${VERSION[*]:0:$((i+1))}") + tags="${tags} ${partial_version}" + done + + echo "Tags to create: ${tags}" + + # Copy each tag + for tag in $tags; do + echo "Copying to ${DST_REGISTRY}/${image}:${tag}" + docker run --rm quay.io/skopeo/stable:latest copy --all \ + docker://${src} \ + docker://${DST_REGISTRY}/${image}:${tag} + done + + # Copy to latest if requested + if [ "$LATEST" = "true" ]; then + echo "Copying to ${DST_REGISTRY}/${image}:latest" + docker run --rm quay.io/skopeo/stable:latest copy --all \ + docker://${src} \ + docker://${DST_REGISTRY}/${image}:latest + fi + done + + echo "### ✅ Images Released" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Released images to \`${DST_REGISTRY}\`:" >> $GITHUB_STEP_SUMMARY + echo "- server:${TAG}" >> $GITHUB_STEP_SUMMARY + echo "- admin-tools:${TAG}" >> $GITHUB_STEP_SUMMARY diff --git a/.goreleaser.yml b/.goreleaser.yml index dfa86b400fa..c28ac7f9ddf 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + before: hooks: - go mod download @@ -83,7 +85,7 @@ checksum: algorithm: sha256 changelog: - skip: true + disable: true announce: - skip: "true" + skip: true