# 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`:** ```yaml # 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:** ```bash docker-compose -f 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): ```json { "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: ```bash 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: ```ini [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. ```yaml # 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:** ```bash docker-compose -f 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): ```dockerfile # 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`: ```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`:** ```yaml # 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:** ```bash # Bring up the application and Watchtower (and others if not running) docker-compose -f 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.