backFebruary 25, 2025

Automating Docker Builds with Gitea Actions

I've been using Gitea as my self-hosted Git solution for some time now, and one feature cool features is Gitea Actions. Today, I'd like to share a specific use case: automatically building and pushing Docker images whenever code is pushed to the main branch.

The workflow overview

Before diving into the implementation, let's understand what we're trying to do. We want a system that:

  1. Detects when we push code to our main branch
  2. Automatically builds a Docker image from that code
  3. Tags the image with both a unique identifier (for versioning) and a "latest" tag
  4. Pushes these images to our private Docker registry
  5. Does all this securely, without exposing credentials

This is a common workflow for many teams and projects, and it eliminates the tedious manual steps of building, tagging, and pushing images.

The workflow file

Here's what the complete Gitea Actions workflow looks like:

name: Build and Push Docker Image

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Login to Registry
        uses: docker/login-action@v2
        with:
          registry: registry.example.com
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build Docker Image
        env:
          BRANCH_NAME: ${{ github.ref_name }}
          SHORT_HASH: ${{ github.sha }}
        run: |
          # Build the image with the commit hash tag
          docker build --build-arg BUILD_IDENTIFIER=${SHORT_HASH:0:5} -t registry.example.com/your-username/your-repository:${BRANCH_NAME}-${SHORT_HASH:0:5} .

          # Tag the same image as "latest"
          docker tag registry.example.com/your-username/your-repository:${BRANCH_NAME}-${SHORT_HASH:0:5} registry.example.com/your-username/your-repository:${BRANCH_NAME}-latest

      - name: Push Docker Images
        env:
          BRANCH_NAME: ${{ github.ref_name }}
          SHORT_HASH: ${{ github.sha }}
        run: |
          docker push registry.example.com/your-username/your-repository:${BRANCH_NAME}-${SHORT_HASH:0:5}
          docker push registry.example.com/your-username/your-repository:${BRANCH_NAME}-latest

      - name: Log out from registry
        if: always()
        run: docker logout registry.example.com

This file needs to be saved as .gitea/workflows/docker.yml in your repository. Let's break it down to understand how it works.

Understanding the workflow

The trigger

on:
  push:
    branches: [ main ]

This section defines when our workflow runs. In this case, whenever code is pushed to the main branch.

The job steps

Our workflow consists of a single job called "build" with several steps. Each step performs a specific task:

1. Checkout the code

- uses: actions/checkout@v4

This step fetches your repository's code so that the next steps can access it.

2. Login to the Docker registry

- name: Login to Registry
  uses: docker/login-action@v2
  with:
    registry: registry.example.com
    username: ${{ secrets.REGISTRY_USER }}
    password: ${{ secrets.REGISTRY_PASSWORD }}

Here, we authenticate with our private Docker registry. Note the use of secrets. This is crucial for security. Instead of hardcoding credentials, we reference secrets that are stored securely in the Gitea repository settings.

3. Build the Docker image

- name: Build Docker Image
  env:
    BRANCH_NAME: ${{ github.ref_name }}
    SHORT_HASH: ${{ github.sha }}
  run: |
    # Build the image with the commit hash tag
    docker build --build-arg BUILD_IDENTIFIER=${SHORT_HASH:0:5} -t registry.example.com/your-username/your-repository:${BRANCH_NAME}-${SHORT_HASH:0:5} .

    # Tag the same image as "latest"
    docker tag registry.example.com/your-username/your-repository:${BRANCH_NAME}-${SHORT_HASH:0:5} registry.example.com/your-username/your-repository:${BRANCH_NAME}-latest

This step does the actual Docker image building. There are several interesting aspects here:

  • We use environment variables to access the branch name and commit hash
  • We truncate the commit hash to the first 5 characters for readability
  • We build the image with a unique tag combining the branch name and commit hash
  • We also create a "latest" tag that will always point to the most recent build

This tagging strategy gives us the best of both worlds: unique identifiers for versioning and a consistent "latest" tag for convenience.

4. Push the images to the registry

- name: Push Docker Images
  env:
    BRANCH_NAME: ${{ github.ref_name }}
    SHORT_HASH: ${{ github.sha }}
  run: |
    docker push registry.example.com/your-username/your-repository:${BRANCH_NAME}-${SHORT_HASH:0:5}
    docker push registry.example.com/your-username/your-repository:${BRANCH_NAME}-latest

After building, we push both versions of our tagged image to the registry.

5. Logout from the registry

- name: Log out from registry
  if: always()
  run: docker logout registry.example.com

As a security best practice, we log out from the registry when we're done. The if: always() condition ensures this step runs even if previous steps failed, which is important for good security.

Setting up the repository

Before this workflow can run successfully, you'll need to configure a few things:

  1. Create the necessary secrets in your Gitea repository:
  • Go to Repository Settings > Secrets
  • Add REGISTRY_USER and REGISTRY_PASSWORD with your Docker registry credentials
  1. Ensure your repository has a valid Dockerfile in its root directory

  2. Make sure the .gitea/workflows directory exists and contains the docker.yml file with the workflow configuration

The benefits

Implementing this workflow has several advantages:

  1. Consistency: Every build follows the same process, eliminating human error from manual builds.

  2. Traceability: Each image is tagged with the branch name and commit hash, making it easy to trace back from a Docker image to the exact code that created it.

  3. Convenience: The "latest" tag always points to the most recent build, simplifying deployment scripts.

  4. Time-saving: No more manual build and push commands - everything happens automatically when you push to main.

Customizing for your needs

This workflow is just a starting point. You can adapt it to your specific requirements:

  • Change the registry URL to your own Docker registry
  • Adjust the image name and tagging scheme
  • Add additional build arguments or environment variables
  • Modify the trigger conditions to run on different branches or events
  • Add testing steps before building