Files
guides-and-docs/gitea-docker-cicd/README.md
2025-04-04 00:58:13 +00:00

14 KiB

Gitea Docker CI/CD Workflow Setup

This guide outlines how to set up a local CI/CD workflow using Gitea running in Docker. The goals are:

  • Use Gitea as the primary Git repository.
  • Optionally mirror repositories to GitHub.
  • Build Docker images for selected repositories (e.g., NextJS applications) using Gitea Runners.
  • Host the built Docker images in a local Docker Registry.
  • Automatically update running Docker containers when new images are available.

Prerequisites

  • Docker installed and running on your host machine.
  • Docker Compose installed.
  • A running Gitea instance, preferably managed via Docker Compose.

Goal Architecture

  1. Developer: Pushes code to a Gitea repository.
  2. Gitea:
    • (Optional/Manual) Mirrors the repository to GitHub.
    • Triggers a Gitea Action workflow on push/tag.
  3. Gitea Runner (Docker):
    • Picks up the workflow job.
    • Checks out the code.
    • Builds a Docker image using Docker-in-Docker or by mounting the host's Docker socket.
    • Pushes the built image to a Local Docker Registry.
  4. Local Docker Registry (Docker): Stores the built application images.
  5. Watchtower (or similar tool - Docker): Monitors the Local Docker Registry for new image versions and automatically updates the running application containers.
  6. Application Container (Docker): Runs the application (e.g., NextJS app) using the image from the Local Docker Registry.

Step 1: Set up Gitea <-> GitHub Synchronization (Mirroring)

Configure Gitea to push changes to a corresponding GitHub repository.

  1. Generate a GitHub Personal Access Token (PAT):
    • Go to GitHub Settings -> Developer settings -> Personal access tokens -> Tokens (classic) or Fine-grained tokens.
    • Generate a token with repo scope (or specific repository write access). Copy the token securely.
  2. Configure Mirroring in Gitea:
    • Navigate to your Gitea repository.
    • Go to Settings -> Repository -> Mirror Settings.
    • Click Add Push Mirror.
    • Git Remote Repository URL: Enter the HTTPS URL of your GitHub repo (e.g., https://github.com/your-username/your-repo.git).
    • Authorization:
      • Username: Your GitHub username.
      • Password/Token: Paste the GitHub PAT.
    • Sync Interval: Set the automatic sync frequency (e.g., 1h, 8h) or leave blank/0 for manual sync only.
    • Click Add Mirror Repository.
  3. Manual Push (Optional): Click the "Synchronize Now" button next to the mirror entry to push immediately.

Note: Gitea becomes the source of truth. Changes made directly on GitHub won't sync back automatically without a pull mirror setup.


Step 2: Set up a Local Docker Registry

This container stores the Docker images built by the runner.

  1. Add Registry Service to docker-compose.yml:

    # In your docker-compose.yml
    version: '3.7'
    
    services:
      # ... your other services (like Gitea) ...
    
      registry:
        image: registry:2
        container_name: local-docker-registry
        ports:
          # Map to a host port, e.g., 5000. Change if needed.
          - "5000:5000"
        volumes:
          # Persist registry data
          - ./registry-data:/var/lib/registry
        restart: always
        networks:
          # Use a common network if Gitea/Runner need to access it by name
          - your_shared_network # Optional: Replace with your network name
    
    volumes:
      registry-data:
      # ... other volumes ...
    
    networks:
      your_shared_network: # Optional: Define if not already defined
        external: false # Or true if defined elsewhere
    
  2. Run the Registry:

    docker-compose -f <your-compose-file.yml> up -d registry
    
  3. Configure Docker Daemon for Insecure Registry:

    • Your Docker daemon needs to trust this HTTP registry. Edit /etc/docker/daemon.json (create if it doesn't exist):

      {
        "insecure-registries" : ["YOUR_HOST_IP:5000"]
      }
      
      • Important: Replace YOUR_HOST_IP with the actual network IP address of the machine running the registry (e.g., 192.168.1.100). Do not use localhost or 127.0.0.1 if the runner is in a separate container.
    • Restart the Docker daemon:

      sudo systemctl restart docker
      # Or equivalent command for your OS (e.g., restart Docker Desktop)
      
    • Security Note: This uses HTTP and is insecure. For sensitive environments, configure TLS for the registry.


Step 3: Set up Gitea Runners

Runners execute Gitea Actions workflows.

  1. Enable Actions in Gitea:

    • Ensure Actions are enabled in your Gitea instance's app.ini configuration file:

      [actions]
      ENABLED = true
      
    • Restart your Gitea container after changing app.ini.

  2. Get Runner Registration Token:

    • In Gitea, go to Site Administration -> Runners (for global runners) or Repository Settings -> Actions -> Runners (for repository-specific runners).
    • Note the Registration Token.
  3. Add Runner Service to docker-compose.yml:

    • This example mounts the host's Docker socket.
    # In your docker-compose.yml
    version: '3.7'
    
    services:
      # ... your gitea, registry services ...
    
      gitea-runner:
        image: gitea/act_runner:latest # Check for the latest official image tag
        container_name: gitea-runner
        environment:
          # Get these from Gitea Admin/Repo Settings -> Actions -> Runners
          GITEA_INSTANCE_URL: http://your_gitea_url:3000 # URL to your Gitea instance
          GITEA_RUNNER_REGISTRATION_TOKEN: YOUR_REGISTRATION_TOKEN_HERE
          GITEA_RUNNER_NAME: my-docker-runner # Optional name
          # Define runner capabilities. 'docker' is used in the example workflow.
          # Adjust labels as needed for your workflows.
          GITEA_RUNNER_LABELS: docker:docker://host.docker.internal/var/run/docker.sock,ubuntu-latest:docker://node:18-bullseye
        volumes:
          # Mount Docker socket from host - allows runner to use host Docker
          - /var/run/docker.sock:/var/run/docker.sock
          # Persist runner config
          - ./gitea-runner-data:/data
        restart: always
        networks:
          - your_shared_network # Ensure it can reach Gitea and the Registry
    
    volumes:
      gitea-runner-data:
      # ... other volumes ...
    
    networks:
      your_shared_network: # Define if not already defined
        # ...
    
    • Replace: http://your_gitea_url:3000 with your Gitea instance URL and YOUR_REGISTRATION_TOKEN_HERE with the token from Gitea.
    • Labels: Ensure the GITEA_RUNNER_LABELS match the runs-on: directive in your workflow files.
  4. Run the Runner:

    docker-compose -f <your-compose-file.yml> up -d gitea-runner
    
  5. Verify Registration: Check the Runners section in Gitea (Admin or Repository Settings) to confirm the runner is registered and idle.


Step 4: Create Gitea Actions Workflow for Building and Pushing

Define the build process for your application (e.g., NextJS).

  1. Create a Dockerfile:

    • Add a Dockerfile to the root of your application's repository. Example for NextJS (multi-stage):
    # Stage 1: Build the application
    FROM node:18-alpine AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm install
    COPY . .
    # Example: Set build-time args if needed
    # ARG NEXT_PUBLIC_API_URL
    # ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
    RUN npm run build
    
    # Stage 2: Production image
    FROM node:18-alpine
    WORKDIR /app
    ENV NODE_ENV=production
    # Uncomment the following line in case you want to disable telemetry during runtime.
    # ENV NEXT_TELEMETRY_DISABLED 1
    COPY --from=builder /app/public ./public
    # Automatically leverage output traces to reduce image size
    # [https://nextjs.org/docs/advanced-features/output-file-tracing](https://nextjs.org/docs/advanced-features/output-file-tracing)
    COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
    COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
    
    # Expose the port Next.js runs on
    EXPOSE 3000
    
    ENV PORT 3000
    
    # Optional: Run as non-root user
    # RUN addgroup --system --gid 1001 nodejs
    # RUN adduser --system --uid 1001 nextjs
    # USER nextjs
    
    # Command to run the application
    CMD ["node", "server.js"]
    
  2. Create the Workflow File:

    • In your repository, create .gitea/workflows/build-push.yaml:
    name: Build and Push Docker Image
    
    on:
      push:
        branches:
          - main # Trigger on pushes to the main branch
        tags:
          - 'v*.*.*' # Trigger on version tags like v1.0.0
    
    jobs:
      build-and-push:
        # Use a runner label defined in your runner's environment variables
        runs-on: docker
        steps:
          - name: Checkout Code
            uses: actions/checkout@v3
    
          - name: Set up Docker Buildx
            uses: docker/setup-buildx-action@v2
    
          # Optional: Login step if your registry requires authentication
          # - name: Log in to local registry
          #   uses: docker/login-action@v2
          #   with:
          #     registry: YOUR_HOST_IP:5000 # Use the same IP as in daemon.json
          #     username: ${{ secrets.REGISTRY_USERNAME }} # Store credentials in Gitea secrets
          #     password: ${{ secrets.REGISTRY_PASSWORD }} # Store credentials in Gitea secrets
    
          - name: Determine Image Tag
            id: meta
            run: |
              # Use Git tag if present, otherwise use 'latest' for default branch
              if [[ ${GITHUB_REF} == refs/tags/* ]]; then
                TAG=${GITHUB_REF#refs/tags/}
              else
                TAG=latest
              fi
              echo "tag=${TAG}" >> $GITHUB_OUTPUT
              echo "Using tag: ${TAG}"
    
          - name: Build and Push Docker Image
            uses: docker/build-push-action@v4
            with:
              context: .
              file: ./Dockerfile
              push: true
              # IMPORTANT: Tag format is REGISTRY_HOST:PORT/IMAGE_NAME:TAG
              tags: YOUR_HOST_IP:5000/your-app-image-name:${{ steps.meta.outputs.tag }}
              # Optional: Add build arguments
              # build-args: |
              #   ARG_NAME=value
              cache-from: type=gha # Optional: Enable build cache
              cache-to: type=gha,mode=max # Optional: Enable build cache
    
    • Replace:
      • YOUR_HOST_IP:5000 with your registry's IP and port.
      • your-app-image-name with your desired image name.
    • runs-on: docker: Ensure this matches a label on your Gitea Runner.
    • Secrets: If using registry authentication, store credentials (REGISTRY_USERNAME, REGISTRY_PASSWORD) in the Gitea repository's Settings -> Secrets.
  3. Commit and Push: Add .gitea/workflows/build-push.yaml and your Dockerfile to the repository, commit, and push to Gitea.

  4. Monitor Workflow: Check the Actions tab in your Gitea repository to view the workflow execution status.


Step 5: Automatically Update Containers with Watchtower

Use Watchtower to monitor the local registry and redeploy containers when a new image is available.

  1. Add Application and Watchtower Services to docker-compose.yml:

    # In your docker-compose.yml
    version: '3.7'
    
    services:
      # ... your gitea, registry, runner services ...
    
      # Your Application container (Example: NextJS)
      my-app:
        # IMPORTANT: Use the full image path from your local registry
        image: YOUR_HOST_IP:5000/your-app-image-name:latest # Watchtower tracks this tag
        container_name: my-app-container
        ports:
          - "8080:3000" # Map host port 8080 to container port 3000 (adjust as needed)
        restart: always
        networks:
          - your_shared_network
        labels:
          # Required label for Watchtower to manage this container when using --label-enable
          - "com.centurylinklabs.watchtower.enable=true"
    
      # Watchtower service
      watchtower:
        image: containrrr/watchtower:latest
        container_name: watchtower
        volumes:
          # Mount Docker socket to allow Watchtower to manage other containers
          - /var/run/docker.sock:/var/run/docker.sock
          # Optional: Mount Docker config if registry requires authentication
          # Make sure docker login has been run on the host first
          # - $HOME/.docker/config.json:/config.json
        # Check every 5 minutes (300s), remove old images, only update containers with the specific label
        command: --cleanup --label-enable --interval 300
        restart: always
        networks:
          - your_shared_network # Needs network access if registry needs authentication
    
    networks:
      your_shared_network: # Define if not already defined
        # ...
    
    • Replace: YOUR_HOST_IP:5000/your-app-image-name:latest with your registry IP, port, image name, and the tag Watchtower should monitor (usually latest).
    • image: directive: Ensure your application service (my-app) uses the full image path from your local registry.
    • labels:: Add the com.centurylinklabs.watchtower.enable=true label to any container you want Watchtower to auto-update.
    • command:: Use --label-enable for safety, so Watchtower only touches labeled containers. Adjust --interval as needed.
  2. Run Application and Watchtower:

    # Bring up the application and Watchtower (and others if not running)
    docker-compose -f <your-compose-file.yml> up -d
    

Summary Workflow

  1. Push code (matching on: criteria in workflow) to Gitea.
  2. Gitea triggers the Actions workflow (build-push.yaml).
  3. Gitea Runner executes the job: checks out code, builds image, pushes tagged image (e.g., :latest or :v1.x.x) to the local registry (YOUR_HOST_IP:5000).
  4. Watchtower (running via Docker) polls the registry.
  5. If Watchtower detects a new image digest for the tag specified in your application container's image: directive (e.g., :latest), it pulls the new image and redeploys the application container using the updated image.
  6. (Optional) Gitea pushes code changes to GitHub via the configured mirror.

You now have an automated build and deployment pipeline hosted locally with Gitea! Remember to replace all placeholders (like YOUR_HOST_IP, image names, network names, tokens) with your actual values.