# ============================================================================= # Docker Workflow — Build & Push to Gitea Container Registry # ============================================================================= # # PURPOSE: # Build Docker images generically; optionally push to the Gitea Container # Registry at git.hiddenden.cafe. # # TRIGGERS: # - pull_request → build only (never push) # - push to main → build; push only if ENABLE_DOCKER=true AND DOCKER_PUSH=true # - tag v* → build; push only if DOCKER_PUSH=true AND DOCKER_PUSH_ON_TAG=true # # DETECTION: # - Dockerfile exists → docker build # - docker-compose.yml exists → docker compose build # - Neither → exit 0 gracefully # # NAMING (Gitea convention): # Image ref: ${REGISTRY_HOST}/${IMAGE_OWNER}/${IMAGE_NAME}:${TAG} # Example: git.hiddenden.cafe/myorg/myrepo:1.2.3 # # AUTHENTICATION: # Uses PAT-based secrets (recommended for Gitea Actions): # - REGISTRY_USERNAME — your Gitea username or bot account # - REGISTRY_TOKEN — a Personal Access Token with package:write scope # Set these in: Repository Settings → Secrets (or Organization Secrets). # NEVER echo secrets in logs. # # CONFIG: # All settings loaded from .ci/config.env. See docs/DOCKER.md. # # ============================================================================= name: Docker on: push: branches: - main tags: - "v*" pull_request: jobs: docker: runs-on: ubuntu-latest steps: # ----------------------------------------------------------------------- # Step 1: Checkout # ----------------------------------------------------------------------- - name: Checkout uses: actions/checkout@v4 # ----------------------------------------------------------------------- # Step 2: Load configuration # ----------------------------------------------------------------------- - name: Load config run: | if [ -f .ci/config.env ]; then set -a source .ci/config.env set +a echo "Config loaded from .ci/config.env" else echo "WARNING: .ci/config.env not found, using defaults" fi # Export with defaults echo "ENABLE_DOCKER=${ENABLE_DOCKER:-true}" >> "$GITHUB_ENV" echo "DOCKER_PUSH=${DOCKER_PUSH:-false}" >> "$GITHUB_ENV" echo "DOCKER_PUSH_ON_BRANCH=${DOCKER_PUSH_ON_BRANCH:-true}" >> "$GITHUB_ENV" echo "DOCKER_PUSH_ON_TAG=${DOCKER_PUSH_ON_TAG:-true}" >> "$GITHUB_ENV" echo "REGISTRY_HOST=${REGISTRY_HOST:-git.hiddenden.cafe}" >> "$GITHUB_ENV" echo "IMAGE_OWNER_CFG=${IMAGE_OWNER:-auto}" >> "$GITHUB_ENV" echo "IMAGE_NAME_CFG=${IMAGE_NAME:-auto}" >> "$GITHUB_ENV" echo "DOCKER_TAG_STRATEGY=${DOCKER_TAG_STRATEGY:-semver+latest}" >> "$GITHUB_ENV" echo "DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}" >> "$GITHUB_ENV" # ----------------------------------------------------------------------- # Step 3: Check if Docker is enabled # ----------------------------------------------------------------------- - name: Check if Docker is enabled run: | if [ "$ENABLE_DOCKER" != "true" ]; then echo "Docker is disabled (ENABLE_DOCKER=$ENABLE_DOCKER). Exiting." # We still exit 0 — graceful skip. echo "SKIP_DOCKER=true" >> "$GITHUB_ENV" fi # ----------------------------------------------------------------------- # Step 4: Detect Dockerfile or docker-compose.yml # ----------------------------------------------------------------------- - name: Detect Docker files if: env.SKIP_DOCKER != 'true' run: | if [ -f Dockerfile ]; then echo "DOCKER_MODE=dockerfile" >> "$GITHUB_ENV" echo "Detected: Dockerfile" elif [ -f docker-compose.yml ] || [ -f docker-compose.yaml ]; then echo "DOCKER_MODE=compose" >> "$GITHUB_ENV" echo "Detected: docker-compose.yml" else echo "No Dockerfile or docker-compose.yml found. Skipping." echo "SKIP_DOCKER=true" >> "$GITHUB_ENV" fi # ----------------------------------------------------------------------- # Step 5: Derive image owner and name dynamically # # Logic: # FULL_REPO is derived from (in priority order): # 1. $GITEA_REPOSITORY (Gitea native env var) # 2. github.repository (Gitea Actions compatibility fallback) # Format: "owner/repo" # # If IMAGE_OWNER=auto → use the owner part # If IMAGE_NAME=auto → use the repo part # Otherwise, use the explicit config values. # ----------------------------------------------------------------------- - name: Derive image naming if: env.SKIP_DOCKER != 'true' run: | # Determine FULL_REPO (owner/repo) # Gitea Actions sets GITEA_REPOSITORY natively in some versions. # It also maps github.repository for compatibility. FULL_REPO="${GITEA_REPOSITORY:-${{ github.repository }}}" if [ -z "$FULL_REPO" ]; then echo "ERROR: Could not determine repository (GITEA_REPOSITORY and github.repository are both empty)" exit 1 fi # Split into OWNER and REPO OWNER="$(echo "$FULL_REPO" | cut -d'/' -f1)" REPO="$(echo "$FULL_REPO" | cut -d'/' -f2)" echo "Derived FULL_REPO=$FULL_REPO OWNER=$OWNER REPO=$REPO" # Apply config overrides if [ "$IMAGE_OWNER_CFG" = "auto" ]; then FINAL_OWNER="$OWNER" else FINAL_OWNER="$IMAGE_OWNER_CFG" fi if [ "$IMAGE_NAME_CFG" = "auto" ]; then FINAL_NAME="$REPO" else FINAL_NAME="$IMAGE_NAME_CFG" fi # Construct the full image reference (without tag) IMAGE_REF="${REGISTRY_HOST}/${FINAL_OWNER}/${FINAL_NAME}" echo "Image reference: ${IMAGE_REF}:" echo "IMAGE_REF=${IMAGE_REF}" >> "$GITHUB_ENV" # ----------------------------------------------------------------------- # Step 6: Determine tags based on trigger and strategy # # Tag rules: # - PR: build only, tag = "pr-" (local only) # - Push to branch: tag = branch name (e.g., "main") # - Tag v1.2.3: tag = "1.2.3"; also "latest" if strategy includes it # ----------------------------------------------------------------------- - name: Determine tags if: env.SKIP_DOCKER != 'true' run: | TAGS="" SHOULD_PUSH=false REF="${GITHUB_REF:-}" echo "Event: ${{ github.event_name }}" echo "Ref: ${REF}" # --- Pull Request: build only --- if [ "${{ github.event_name }}" = "pull_request" ]; then TAGS="${IMAGE_REF}:pr-${{ github.event.number }}" SHOULD_PUSH=false echo "PR build — will NOT push" # --- Tag push (v*) --- elif echo "${REF}" | grep -qE '^refs/tags/v'; then # Extract version: refs/tags/v1.2.3 → 1.2.3 VERSION="$(echo "${REF}" | sed 's|refs/tags/v||')" TAGS="${IMAGE_REF}:${VERSION}" # Optionally add :latest if echo "$DOCKER_TAG_STRATEGY" | grep -qi 'latest'; then TAGS="${TAGS},${IMAGE_REF}:latest" fi if [ "$DOCKER_PUSH" = "true" ] && [ "$DOCKER_PUSH_ON_TAG" = "true" ]; then SHOULD_PUSH=true fi echo "Tag push — version=${VERSION}, push=${SHOULD_PUSH}" # --- Branch push --- elif echo "${REF}" | grep -q '^refs/heads/'; then BRANCH="$(echo "${REF}" | sed 's|refs/heads/||')" TAGS="${IMAGE_REF}:${BRANCH}" if [ "$DOCKER_PUSH" = "true" ] && [ "$DOCKER_PUSH_ON_BRANCH" = "true" ]; then SHOULD_PUSH=true fi echo "Branch push — branch=${BRANCH}, push=${SHOULD_PUSH}" else echo "Unknown ref type: ${REF}. Building with tag 'dev'." TAGS="${IMAGE_REF}:dev" SHOULD_PUSH=false fi echo "DOCKER_TAGS=${TAGS}" >> "$GITHUB_ENV" echo "SHOULD_PUSH=${SHOULD_PUSH}" >> "$GITHUB_ENV" echo "Final tags: ${TAGS}" echo "Will push: ${SHOULD_PUSH}" # ----------------------------------------------------------------------- # Step 7: Build the Docker image # ----------------------------------------------------------------------- - name: Build Docker image if: env.SKIP_DOCKER != 'true' run: | if [ "$DOCKER_MODE" = "dockerfile" ]; then # Build with the first tag; additional tags are added after PRIMARY_TAG="$(echo "$DOCKER_TAGS" | cut -d',' -f1)" echo ">>> docker build -t ${PRIMARY_TAG} ." docker build -t "${PRIMARY_TAG}" . # Tag additional images if present IFS=',' read -ra TAG_ARRAY <<< "$DOCKER_TAGS" for tag in "${TAG_ARRAY[@]:1}"; do echo ">>> docker tag ${PRIMARY_TAG} ${tag}" docker tag "${PRIMARY_TAG}" "${tag}" done elif [ "$DOCKER_MODE" = "compose" ]; then echo ">>> docker compose build" docker compose build fi # ----------------------------------------------------------------------- # Step 8: Login to registry (only when pushing) # # Uses PAT-based auth. Requires secrets: # REGISTRY_USERNAME — Gitea username or bot # REGISTRY_TOKEN — PAT with package:write scope # ----------------------------------------------------------------------- - name: Login to container registry if: env.SKIP_DOCKER != 'true' && env.SHOULD_PUSH == 'true' run: | echo "Logging in to ${REGISTRY_HOST}..." # Use --password-stdin to avoid leaking the token in process list echo "${{ secrets.REGISTRY_TOKEN }}" | \ docker login "${REGISTRY_HOST}" \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin # ----------------------------------------------------------------------- # Step 9: Push Docker image(s) # ----------------------------------------------------------------------- - name: Push Docker image if: env.SKIP_DOCKER != 'true' && env.SHOULD_PUSH == 'true' run: | IFS=',' read -ra TAG_ARRAY <<< "$DOCKER_TAGS" for tag in "${TAG_ARRAY[@]}"; do echo ">>> docker push ${tag}" docker push "${tag}" done # ----------------------------------------------------------------------- # Step 10: Summary # ----------------------------------------------------------------------- - name: Docker Summary if: always() run: | echo "==============================" echo " Docker Workflow Complete" echo " Mode: ${DOCKER_MODE:-skipped}" echo " Tags: ${DOCKER_TAGS:-none}" echo " Pushed: ${SHOULD_PUSH:-false}" echo "=============================="