How I Deployed Woodpecker CI on Fedora IoT

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5168

    #1

    How I Deployed Woodpecker CI on Fedora IoT

    I wanted a self-hosted CI/CD system that was lightweight and container-native, without the overhead of enterprise solutions. This is the story of how I deployed Woodpecker CI1 on my Fedora IoT2 server. Along the way, I had to navigate DNS conflicts, SELinux hurdles, and the challenge of secure external access.


    In this setup, I used OpenTofu3 for infrastructure automation and Cloudflare Tunnel4 to bridge the gap between my local network and the web.


    Table of Contents

    • Why I Chose Woodpecker CI
    • My Infrastructure Layout
    • The Road to Deployment
    • Step 1: Handing Security & Secrets
    • Step 2: Bridging with Cloudflare Tunnel
    • Step 3: Orchestrating the Server
    • Step 4: Taming the Agent & Networking
    • How I Verified Everything
    • Final Thoughts & Troubleshooting


    Why I Chose Woodpecker CI

    I needed a platform that felt modern but stayed out of my way. Woodpecker CI fit the bill because:
    • Isolation: I liked that every build runs in its own ephemeral container.
    • Integration: It has seamless support for the GitHub OAuth flow I already use.
    • Simplicity: I could define my pipelines in a familiar .woodpecker.yaml format.


    Researching the bits: I spent some time with the Woodpecker architecture docs5 to understand how the server and agent communicate over gRPC.


    My Infrastructure Layout

    I decided to use Cloudflare Tunnel so I wouldn't have to touch my firewall or handle SSL certificates manually. My Woodpecker Server acts as the brain, while the Agent does the heavy lifting via the Podman socket.





    My Core Decisions

    • DNS: I solved the port 53 conflict by using a custom bridge network for internal resolution.
    • Access: I mapped https://ci.homelab.example directly to my local instance.


    The Road to Deployment

    My process involved four main phases. I used OpenTofu to make the deployment repeatable and Podman Quadlets6 to manage the container lifecycles as systemd services.





    Step 1: Handing Security & Secrets

    I started with the security groundwork. First, I set up a new OAuth application on GitHub to handle authentication.


    My GitHub OAuth Setup

    1. I went to GitHub settings > Developer settings > OAuth Apps > New OAuth App7.
    2. Homepage URL: https://ci.homelab.example
    3. Authorization callback URL: https://ci.homelab.example/authorize
    4. I made sure to store the Client ID and Client Secret securely for later.


    Generating the Agent Secret

    The server and agent need a shared secret to talk to each other. I generated a secure random string using openssl:






    openssl rand -hex 32







    Step 2: Bridging with Cloudflare Tunnel

    I chose to use a tunnel because it's much simpler than managing port forwarding on my router.


    My Initial Authentication

    I had to run this once on my Fedora IoT server to link it to my Cloudflare account:






    cloudflared tunnel login







    Automation with OpenTofu

    I wrote an OpenTofu resource to automate the installation of cloudflared, create the tunnel, and set up the systemd service. Here is the configuration I used:






    resource "null_resource" "setup_cloudflare_tunnel" {
    connection {
    type = "ssh"
    user = "admin"
    host = "192.168.1.100"
    private_key = file("~/.ssh/id_ed25519")
    }

    provisioner "file" {
    content = local.tunnel_script_content
    destination = "/tmp/setup_cloudflare_tunnel.sh"
    }

    provisioner "remote-exec" {
    inline = [
    "chmod +x /tmp/setup_cloudflare_tunnel.sh",
    "/tmp/setup_cloudflare_tunnel.sh",
    ]
    }
    }

    locals {
    tunnel_script_content = <<-EOF
    #!/bin/bash
    set -euo pipefail

    # Fedora IoT uses rpm-ostree, install cloudflared from binary if missing
    if ! command -v cloudflared &> /dev/null; then
    ARCH=$(uname -m)
    case $ARCH in
    x86_64) DOWNLOAD_URL="https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64" ;;
    aarch64) DOWNLOAD_URL="https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64" ;;
    *) echo "❌ Unsupported architecture: $ARCH"; exit 1 ;;
    esac
    curl -L "$DOWNLOAD_URL" -o /tmp/cloudflared
    sudo install -m 755 /tmp/cloudflared /usr/local/bin/cloudflared
    fi

    # Create tunnel and route DNS (requires manual 'cloudflared tunnel login' first)
    TUNNEL_NAME="woodpecker"
    cloudflared tunnel create "$TUNNEL_NAME" || true
    TUNNEL_ID=$(cloudflared tunnel list | grep "$TUNNEL_NAME" | awk '{print $1}')
    cloudflared tunnel route dns "$TUNNEL_NAME" ci.homelab.example 2>/dev/null || true

    # Setup system user and config
    sudo useradd --system --home /var/lib/cloudflared --shell /usr/sbin/nologin cloudflared 2>/dev/null || true
    sudo mkdir -p /etc/cloudflared /var/lib/cloudflared
    sudo cp "$HOME/.cloudflared/$TUNNEL_ID.json" /etc/cloudflared/
    sudo chown -R cloudflared:cloudflared /etc/cloudflared /var/lib/cloudflared

    sudo tee /etc/cloudflared/config.yml > /dev/null <
Working...