name: Docker Build + Push description: Build and push Docker images with buildx and layer caching for Gitea Actions author: eric inputs: dockerfile: description: Path to Dockerfile (relative to build_context) default: Dockerfile context: description: Build context path (relative to repo root) default: . image-name: description: Full image name (e.g. registry/image or image) required: true tags: description: Additional tags as CSV appended to defaults default: "" cache-from: description: Cache source (e.g. type=gha, or image:tag) default: type=local,src=/tmp/buildx-cache cache-to: description: Cache destination for buildx (type=gha or type=local) default: type=local,dest=/tmp/buildx-cache,mode=max registry: description: Docker registry for login (e.g. ghcr.io) default: "" registry-username: description: Docker registry username default: "" registry-password: description: Docker registry password (use secrets.DOCKER_PASSWORD) default: "" platforms: description: Target platforms as CSV (e.g. linux/amd64,linux/arm64) default: linux/amd64 push: description: Push images to registry after build default: "false" build-args: description: Build args as CSV KEY=VALUE default: "" use-buildx: description: Use docker buildx (set to false for runners without buildx support) default: "true" outputs: image: description: Full image name with tag value: ${{ steps.meta.outputs.image }} tag: description: The primary tag used (date-SHA) value: ${{ steps.meta.outputs.tag }} digest: description: Image digest (only available after push) value: ${{ steps.push-digest.outputs.digest }} runs: using: composite env: DOCKER_HOST: tcp://docker.local:2375 DOCKER_BUILDKIT: "1" BUILDKIT_HOST: tcp://docker.local:2375 steps: - name: Set up QEMU for multi-platform builds shell: bash if: inputs.use-buildx == 'true' && inputs.platforms != 'linux/amd64' run: | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 2>/dev/null || true - name: Set up Docker Buildx id: buildx shell: bash if: inputs.use-buildx == 'true' run: | mkdir -p $HOME/.docker/cli-plugins curl -fsSL https://github.com/docker/buildx/releases/download/v0.12.1/buildx-v0.12.1.linux-amd64 \ -o $HOME/.docker/cli-plugins/docker-buildx chmod +x $HOME/.docker/cli-plugins/docker-buildx docker buildx version # Clean up any existing broken builder docker buildx rm -f gitea-builder 2>/dev/null || true # Try docker-container driver first (for multi-platform), fall back to docker driver OUTPUT=$(docker buildx create \ --name gitea-builder \ --driver docker-container \ --driver-opt image=moby/buildkit:buildx-stable-1 \ --driver-opt network=host \ --use 2>&1) echo "$OUTPUT" if echo "$OUTPUT" | grep -qi "error\|failed\|permission\|denied"; then # docker-container failed, fall back to docker driver docker buildx rm -f gitea-builder 2>/dev/null || true docker buildx create --name gitea-builder --driver docker --use fi # Verify builder works INSPECT_OUTPUT=$(docker buildx inspect --bootstrap 2>&1) || true echo "$INSPECT_OUTPUT" if echo "$INSPECT_OUTPUT" | grep -qi "error\|failed"; then docker buildx rm -f gitea-builder 2>/dev/null || true docker buildx create --name gitea-builder --driver docker --use docker buildx inspect --bootstrap fi - name: Compute metadata id: meta shell: bash run: | # Generate tags TAGS="latest" if [ -n "${{ inputs.tags }}" ]; then TAGS="$TAGS,${{ inputs.tags }}" fi SHORT_SHA="$(git rev-parse --short HEAD 2>/dev/null || echo 'local')" DATE_TAG="$(date +%Y-%m-%d)" TAG="$DATE_TAG-$SHORT_SHA" TAGS="$TAGS,$TAG" # Add git tag if this is a tag push if [[ "${{ github.ref }}" == refs/tags/* ]]; then TAG_NAME="${{ github.ref_name }}" TAGS="$TAGS,$TAG_NAME" fi echo "image=${{ inputs.image-name }}" >> $GITHUB_OUTPUT echo "tag=$TAG" >> $GITHUB_OUTPUT echo "tags=$TAGS" >> $GITHUB_OUTPUT - name: Log in to Docker registry shell: bash run: | if [ -n "${{ inputs.registry }}" ] && [ -n "${{ inputs.registry-password }}" ]; then echo "${{ inputs.registry-password }}" | docker login ${{ inputs.registry }} -u ${{ inputs.registry-username }} --password-stdin fi - name: Build image id: build shell: bash run: | # Disable BuildKit for plain docker builds (avoids container networking issues) export DOCKER_BUILDKIT=0 if [ "${{ inputs.use-buildx }}" = "true" ]; then export DOCKER_BUILDKIT=1 fi CACHE_FROM_FLAG="" if [ -n "${{ inputs.cache-from }}" ] && [ "${{ inputs.use-buildx }}" = "true" ]; then CACHE_FROM_FLAG="--cache-from=${{ inputs.cache-from }}" fi BUILD_ARGS_FLAG="" if [ -n "${{ inputs.build-args }}" ]; then for arg in $(echo "${{ inputs.build-args }}" | tr ',' ' '); do BUILD_ARGS_FLAG="$BUILD_ARGS_FLAG --build-arg=$arg" done fi PLATFORM_FLAG="" if [ "${{ inputs.use-buildx }}" = "true" ] && [ "${{ inputs.platforms }}" != "linux/amd64" ]; then PLATFORM_FLAG="--platform=${{ inputs.platforms }}" fi if [ "${{ inputs.use-buildx }}" = "true" ]; then # Use docker buildx if [ "${{ inputs.push }}" = "true" ]; then OUTPUT_FLAG="--push --output=type=registry" else OUTPUT_FLAG="--load --output=type=docker" fi set -x docker buildx build \ $PLATFORM_FLAG \ --tag "${{ inputs.image-name }}:${{ steps.meta.outputs.tag }}" \ --tag "${{ inputs.image-name }}:latest" \ $(for t in $(echo ${{ steps.meta.outputs.tags }} | tr ',' ' '); do echo -n "--tag ${{ inputs.image-name }}:$t "; done) \ --file ${{ inputs.context }}/${{ inputs.dockerfile }} \ --cache-to=${{ inputs.cache-to }} \ $CACHE_FROM_FLAG \ $BUILD_ARGS_FLAG \ --progress=plain \ $OUTPUT_FLAG \ ${{ inputs.context }} else # Plain docker build (no buildx) if [ "${{ inputs.push }}" = "true" ]; then set -x docker build \ --tag "${{ inputs.image-name }}:${{ steps.meta.outputs.tag }}" \ --tag "${{ inputs.image-name }}:latest" \ $(for t in $(echo ${{ steps.meta.outputs.tags }} | tr ',' ' '); do echo -n "--tag ${{ inputs.image-name }}:$t "; done) \ --file ${{ inputs.context }}/${{ inputs.dockerfile }} \ $BUILD_ARGS_FLAG \ ${{ inputs.context }} docker push "${{ inputs.image-name }}:${{ steps.meta.outputs.tag }}" docker push "${{ inputs.image-name }}:latest" else set -x docker build \ --tag "${{ inputs.image-name }}:${{ steps.meta.outputs.tag }}" \ --tag "${{ inputs.image-name }}:latest" \ $(for t in $(echo ${{ steps.meta.outputs.tags }} | tr ',' ' '); do echo -n "--tag ${{ inputs.image-name }}:$t "; done) \ --file ${{ inputs.context }}/${{ inputs.dockerfile }} \ $BUILD_ARGS_FLAG \ ${{ inputs.context }} fi fi - name: Get image digest id: push-digest shell: bash if: inputs.push == 'true' run: | DIGEST="$(docker inspect "${{ inputs.image-name }}:${{ steps.meta.outputs.tag }}" --format '{{.Digest}}' 2>/dev/null)" echo "digest=$DIGEST" >> $GITHUB_OUTPUT echo "digest_full=${{ inputs.image-name }}@$DIGEST" >> $GITHUB_OUTPUT