AI

OSS CI/CD比較:Gitea Actions vs Drone CI vs Woodpecker CI でGitHub Actionsを自前運用する

オープンソースラボ編集部2026年6月14日

OSS CI/CD比較:Gitea Actions vs Drone CI vs Woodpecker CI でGitHub Actionsを自前運用する

GitHub Actions(月$0.008/分〜)・CircleCI(月$15〜)・Jenkins(OSS・設定複雑)に対して、Gitea Actions(GitHub Actions互換・Giteaと統合)・Drone CI(Docker-native・YAML宣言的パイプライン)・Woodpecker CI(Drone forkの軽量版)はCI/CDをセルフホストできます。

OSS CI/CDをセルフホストする理由

  • コスト: GitHub Actions(月500分無料・超過$0.008/分 → 月10万分=$800/月)→ VPS+Droneで$30/月
  • プライベートリポジトリ無制限: GitHub.com の制限なしに内部リポジトリでCI/CD
  • カスタムランナー: GPUランナー・特殊ハードウェア・社内ネットワーク専用のカスタム環境
  • コンプライアンス: ビルドログ・アーティファクトを社内ネットワーク内に保持

主要ツールの概要

Gitea + Gitea Actions

2016年公開(Gitea Actions: 2023年)、Go製のOSSです。GitHubスター44k+。GitHub Actionsと互換性のあるワークフロー構文を使ってセルフホストGitサーバーでCI/CDを実行できます。既存のGitHub Actionsワークフローをほぼそのままで移行できます。

# docker-compose.yml - Gitea + Gitea Actions Runner
version: '3.8'
services:
  gitea:
    image: gitea/gitea:1.22-rootless
    restart: unless-stopped
    ports:
      - "3000:3000"
      - "2222:2222"
    environment:
      USER_UID: "1000"
      USER_GID: "1000"
      GITEA__database__DB_TYPE: postgres
      GITEA__database__HOST: gitea-db:5432
      GITEA__database__NAME: gitea
      GITEA__database__USER: gitea
      GITEA__database__PASSWD: ${DB_PASSWORD}
      GITEA__server__DOMAIN: git.yourcompany.com
      GITEA__server__ROOT_URL: https://git.yourcompany.com
      GITEA__server__HTTP_PORT: "3000"
      GITEA__server__SSH_PORT: "2222"
      GITEA__mailer__ENABLED: "true"
      GITEA__mailer__FROM: git@yourcompany.com
      GITEA__mailer__SMTP_ADDR: smtp.sendgrid.net
      GITEA__mailer__SMTP_PORT: "587"
      GITEA__mailer__USER: apikey
      GITEA__mailer__PASSWD: ${SENDGRID_KEY}
      # Actions有効化
      GITEA__actions__ENABLED: "true"
    volumes:
      - gitea_data:/var/lib/gitea
    depends_on:
      - gitea-db

  gitea-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: gitea
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - gitea_pgdata:/var/lib/postgresql/data

  gitea-runner:
    image: gitea/act_runner:latest
    restart: unless-stopped
    environment:
      GITEA_INSTANCE_URL: https://git.yourcompany.com
      GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_TOKEN}
      GITEA_RUNNER_NAME: my-runner
      GITEA_RUNNER_LABELS: "ubuntu-latest,ubuntu-22.04,linux"
      CONFIG_FILE: /etc/act_runner/config.yaml
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./act_runner_config.yaml:/etc/act_runner/config.yaml
      - runner_data:/data
    depends_on:
      - gitea

volumes:
  gitea_data:
  gitea_pgdata:
  runner_data:
# .gitea/workflows/deploy.yml - GitHub Actions互換のワークフロー
name: Build and Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check
      - run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.REGISTRY_URL }}
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASSWORD }}
      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ secrets.REGISTRY_URL }}/myapp:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to Production
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.PROD_HOST }}
          username: deploy
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            docker pull ${{ secrets.REGISTRY_URL }}/myapp:${{ github.sha }}
            docker service update --image ${{ secrets.REGISTRY_URL }}/myapp:${{ github.sha }} myapp_web

Drone CI

2014年公開、Go製のOSSです。GitHubスター31k+。すべてのパイプラインステップをDockerコンテナとして実行する設計で、環境の再現性が高くステップ間の依存関係が明確です。.drone.ymlの宣言的なYAML構文がシンプルで学習コストが低いです。

# docker-compose.yml - Drone CI + Gitea統合
version: '3.8'
services:
  drone-server:
    image: drone/drone:2
    restart: unless-stopped
    ports:
      - "8080:80"
      - "8443:443"
    environment:
      DRONE_GITEA_SERVER: https://git.yourcompany.com
      DRONE_GITEA_CLIENT_ID: ${GITEA_OAUTH_CLIENT_ID}
      DRONE_GITEA_CLIENT_SECRET: ${GITEA_OAUTH_CLIENT_SECRET}
      DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
      DRONE_SERVER_HOST: drone.yourcompany.com
      DRONE_SERVER_PROTO: https
      DRONE_DATABASE_DRIVER: postgres
      DRONE_DATABASE_DATASOURCE: postgres://drone:${DB_PASSWORD}@drone-db/drone?sslmode=disable
      DRONE_REDIS_CONNECTION: redis://drone-redis:6379
    volumes:
      - drone_data:/data
    depends_on:
      - drone-db
      - drone-redis

  drone-runner:
    image: drone/drone-runner-docker:1
    restart: unless-stopped
    environment:
      DRONE_RPC_PROTO: https
      DRONE_RPC_HOST: drone.yourcompany.com
      DRONE_RPC_SECRET: ${DRONE_RPC_SECRET}
      DRONE_RUNNER_CAPACITY: 4
      DRONE_RUNNER_NAME: docker-runner
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  drone-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: drone
      POSTGRES_USER: drone
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - drone_pgdata:/var/lib/postgresql/data

  drone-redis:
    image: redis:7-alpine

volumes:
  drone_data:
  drone_pgdata:
# .drone.yml - Node.js アプリのCI/CDパイプライン
kind: pipeline
type: docker
name: default

steps:
  - name: install
    image: node:20-alpine
    commands:
      - npm ci --frozen-lockfile

  - name: lint-and-test
    image: node:20-alpine
    environment:
      DATABASE_URL:
        from_secret: TEST_DATABASE_URL
    commands:
      - npm run lint
      - npm run type-check
      - npm run test:ci
    depends_on:
      - install

  - name: build
    image: node:20-alpine
    environment:
      NEXT_PUBLIC_API_URL:
        from_secret: STAGING_API_URL
    commands:
      - npm run build
    depends_on:
      - lint-and-test

  - name: docker-build-push
    image: plugins/docker
    settings:
      registry:
        from_secret: REGISTRY_URL
      repo:
        from_secret: REGISTRY_REPO
      tags:
        - latest
        - ${DRONE_COMMIT_SHA:0:8}
      username:
        from_secret: REGISTRY_USER
      password:
        from_secret: REGISTRY_PASSWORD
    depends_on:
      - build
    when:
      branch:
        - main

  - name: deploy-staging
    image: appleboy/drone-ssh
    settings:
      host:
        from_secret: STAGING_HOST
      username: deploy
      key:
        from_secret: STAGING_SSH_KEY
      script:
        - docker pull $${REGISTRY_REPO}:$${DRONE_COMMIT_SHA:0:8}
        - docker compose -f /opt/app/docker-compose.yml up -d
    when:
      branch:
        - main
    depends_on:
      - docker-build-push
# Drone API でビルド状況を監視・Slack通知(Python)
import requests
import os

DRONE_URL = 'https://drone.yourcompany.com'
DRONE_TOKEN = os.environ['DRONE_API_TOKEN']
headers = {'Authorization': f'Bearer {DRONE_TOKEN}'}

def get_recent_builds(owner: str, repo: str, limit: int = 10) -> list:
    resp = requests.get(
        f'{DRONE_URL}/api/repos/{owner}/{repo}/builds',
        headers=headers,
        params={'limit': limit},
    )
    resp.raise_for_status()
    return resp.json()

def get_build_status_summary(owner: str, repo: str) -> dict:
    builds = get_recent_builds(owner, repo, 50)
    statuses = [b['status'] for b in builds]
    return {
        'total': len(statuses),
        'success': statuses.count('success'),
        'failure': statuses.count('failure'),
        'running': statuses.count('running'),
        'success_rate': round(statuses.count('success') / len(statuses) * 100, 1),
    }

# 使用例
summary = get_build_status_summary('myorg', 'myapp')
print(f"ビルド成功率: {summary['success_rate']}% ({summary['success']}/{summary['total']})")

機能比較表

比較項目Gitea ActionsDrone CIWoodpecker CI
GitHub Actions互換✅(95%)❌(独自YAML)❌(Drone互換)
Docker-native
UI管理画面✅(Gitea統合)
設定の学習コスト低(GitHub準拠)
Gitサーバー一体型
GitHub Stars44k+31k+4k+

OSS CI/CDはDevOpsカテゴリ/categories/devopsのコンテナレジストリ(Harbor)・観測可能性スタック(OpenTelemetry+Grafana)と統合してビルド→テスト→デプロイ→監視のフルサイクルをオンプレ完結で実現します。セキュリティカテゴリ/categories/securityのHashiCorp VaultのDynamic SecretsをDrone CIと統合してビルド時のDB認証情報を都度発行・失効させることでシークレット漏洩リスクを排除できます。

FAQ

Q. GitHub ActionsからGitea Actionsへの移行コストはどれくらいですか?

A. GitHub ActionsとGitea Actionsは同じワークフロー構文(YAMLのonjobsstepsuses)を使うため、多くのワークフローは.github/workflows/.gitea/workflows/に移動するだけで動作します。注意点: ①uses: actions/checkout@v4などの公式アクションはGitea ActionsでGitHub.comのアクションリポジトリから取得できる(ネットワーク接続必要)②GITHUB_*環境変数(GITHUB_SHAGITHUB_REF等)はGITEA_*に変更③GitHub PackagesはGitea Container Registryに移行④GitHub Environments(production/staging)はGitea 1.22以降でサポート。移行成功率: 一般的なNode.js/Python/GoのCI/CDワークフローは80〜90%が無改修または軽微な修正で動作します。

Q. Drone CIのシークレット管理はどうすればいいですか?

A. ①プロジェクト単位のシークレット: Drone UIまたはAPI(POST /api/repos/{owner}/{repo}/secrets)でシークレットを登録し、.drone.ymlfrom_secret:参照②Organization シークレット: 複数リポジトリで共有するシークレットをOrg levelで登録③HashiCorp Vault統合: drone-vaultプラグインでVaultからシークレットを動的に取得(from_secret: vault:secret/data/myapp#API_KEY形式)④環境変数へのエクスポート: .drone.ymlenvironment.MY_KEY.from_secret: MY_KEYでシークレットを環境変数としてステップに渡す。注意: シークレットはPRビルドではデフォルトで渡されない(fork PR経由のシークレット漏洩を防ぐため)。信頼済みリポジトリのPRのみシークレットを渡す設定(allow_pull_requests: true)も可能。

Q. Woodpecker CIとDrone CIはどう違いますか?

A. Woodpecker CIはDrone CI 0.8系からフォークされたOSSプロジェクトです。主な違い: ①ライセンス: DroneはEnterprise版が有料・CE版の機能制限あり(Organizationは有料)。WoodpeckerはApache 2.0で完全無料②設定ファイル: WoodpeckerはDroneとほぼ互換性があり(.woodpecker.yml)、移行コストが低い③マルチパイプライン: WoodpeckerはCI構成を複数ファイルに分割できる(.woodpecker/*.yml)④プラグインエコシステム: DroneのプラグインはWoodpeckerでも動作するものが多い。選択基準: ①Organization・複数チームで使うならWoodpecker(無制限無料)②既存のDroneパイプラインを流用するならWoodpecker(移行コスト最小)③Drone Enterprise機能(RBAC・Audit Logs)が必要ならDrone Enterprise検討。

Q. セルフホストCI/CDのランナーのスペックはどう決めますか?

A. ビルドの種類によって必要スペックが異なります。①Node.js/Python Webアプリ: 2コア4GB RAM(同時4並列ビルドなら8コア16GB)②Docker imageビルド: 4コア8GB RAM(Buildx multi-platformは×2〜3)③Flutter/React Nativeビルド: 4コア16GB RAM(iOS/Android同時なら8コア32GB)④機械学習モデル学習: GPU(NVIDIA T4〜A100)+ 32GB+ RAM。コスト最適化: ①スポットインスタンス(AWS Spot/GCP Preemptible)をランナーとして使い、ビルド時のみ起動②autoscaling: Drone + Amazon EC2 Plugin・Woodpecker + Kubernetes Runnerで需要に応じたランナー自動起動③キャッシュ戦略: node_modules・Docker layer cacheをボリュームマウントまたはRegistry Cacheで共有してビルド時間50〜70%短縮。

まとめ

ユースケース推奨ツール
GitHub Actions移行・Gitサーバー一体型Gitea + Gitea Actions
Docker-native・シンプルYAML・拡張性Drone CI
Drone互換・完全無料・Organization無制限Woodpecker CI

関連外部リソース

他の記事も読む

Let's Build Together

OSS導入、自社だけで悩まない。

ツール選定から構築・運用・AI活用まで、オープンソースラボ運営元のClasslessが伴走します。初回のご相談は無料です。