GitHub Packages ile Özel Paket Deposu Oluşturma

Ekip büyüdükçe, iç kütüphaneler çoğaldıkça bir noktada şu soru kaçınılmaz olarak geliyor: “Bu paketi nereye koyacağız?” Herkese açık npm, PyPI ya da Maven depolarına atmak istemiyorsunuz çünkü iç kullanıma özel. Kendi Nexus ya da Artifactory kurulumu için ne zaman ne para var. İşte tam bu noktada GitHub Packages devreye giriyor ve çoğu zaman göz ardı edilen bir çözüm sunuyor.

Bu yazıda npm, Maven ve Docker image’ları için GitHub Packages üzerinde özel paket deposu kurulumunu, CI/CD entegrasyonunu ve ekip içi yetkilendirme yapısını ele alacağız. Üretimde kullandığım gerçek konfigürasyonları paylaşacağım.

GitHub Packages Nedir ve Ne Zaman Mantıklıdır?

GitHub Packages, GitHub’ın kendi içine gömülü paket barındırma servisidir. npm, Maven, Gradle, RubyGems, NuGet ve Docker/OCI image’larını destekler. Repository ile aynı namespace altında yaşar, bu da erişim kontrolünü ciddi ölçüde basitleştirir.

Ne zaman kullanmalısınız?

  • Zaten GitHub kullanıyorsanız ve ek bir servis ayağa kaldırmak istemiyorsanız
  • Repository bazlı paket erişim kontrolü yeterliyse
  • Küçük-orta ölçekli ekipler için (büyük ölçekte fiyatlandırma sorun olabilir)
  • Private repository’lerinizle sıkı entegrasyon istiyorsanız

Ne zaman alternatif düşünmelisiniz?

  • Çok sayıda büyük Docker image’ı saklayacaksanız (depolama maliyeti artar)
  • Çok granüler paket bazlı izin yönetimi gerekiyorsa
  • GitHub dışı CI/CD sistemleri ağırlıklıysa

Şimdi işin pratik kısmına geçelim.

Personal Access Token ile Kimlik Doğrulama

GitHub Packages’a erişmek için bir PAT (Personal Access Token) oluşturmanız gerekiyor. Fine-grained token kullanabilirsiniz ama klasik token daha geniş araç desteğine sahip, tercihim o yönde.

GitHub üzerinde Settings > Developer settings > Personal access tokens > Tokens (classic) yolunu izleyin. Gerekli scope’lar:

  • read:packages: Paket indirmek için
  • write:packages: Paket yayımlamak için
  • delete:packages: Paket silmek için (dikkatli kullanın)
  • repo: Private repository paketlerine erişmek için zorunlu

Token’ı bir değişkene alalım ve test edelim:

export GH_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
export GH_USER="kullanici-adiniz"

# Docker registry için login testi
echo $GH_TOKEN | docker login ghcr.io -u $GH_USER --password-stdin
# Login Succeeded çıktısını görmelisiniz

CI/CD ortamlarında bu token’ı asla kod içine gömmeyin. GitHub Actions kullanıyorsanız zaten GITHUB_TOKEN otomatik olarak inject ediliyor, onu kullanın.

npm Paketi Yayımlamak

Diyelim ki şirket içi bir utility kütüphaneniz var, @sirket/utils adında. Bu paketi npm yerine GitHub Packages’a göndermek istiyorsunuz.

Önce proje dizinindeki package.json dosyasını düzenleyin:

{
  "name": "@sirket/utils",
  "version": "1.0.0",
  "description": "Sirket ici utility kutuphanesi",
  "main": "index.js",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/sirket/utils.git"
  }
}

Dikkat edin, name alanındaki scope (@sirket) GitHub organizasyon veya kullanıcı adınızla birebir eşleşmeli. Bu eşleşme olmazsa publish işlemi hata verir.

Sonra .npmrc dosyası oluşturun, bunu proje kökünde değil, home dizininizde (~/.npmrc) tutmak daha güvenli:

# ~/.npmrc
@sirket:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=ghp_xxxxxxxxxxxxxxxxxxxx

Ya da environment variable ile dinamik hale getirin:

# Proje dizinindeki .npmrc (token'i env'den alır)
@sirket:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GH_TOKEN}

Şimdi publish edelim:

npm publish
# Çıktı:
# npm notice Publishing to https://npm.pkg.github.com
# + @sirket/[email protected]

Başka bir projede bu paketi kullanmak için o projenin .npmrc dosyasına aynı registry tanımını eklemeniz yeterli:

# Tüketen projenin .npmrc dosyası
@sirket:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GH_TOKEN}

Ardından normal npm install ile çekilebilir:

npm install @sirket/utils

Maven Paketi için Ayarlar

Java/Kotlin ekipleri için Maven deposu kurulumu biraz daha fazla konfigürasyon istiyor ama mantık aynı.

pom.xml dosyanıza distribution management ekleyin:

<distributionManagement>
  <repository>
    <id>github</id>
    <name>GitHub Packages</name>
    <url>https://maven.pkg.github.com/ORGANIZASYON/REPOSITORY</url>
  </repository>
</distributionManagement>

Maven’ın settings.xml dosyasına (genellikle ~/.m2/settings.xml) kimlik bilgilerini ekleyin:

<settings>
  <servers>
    <server>
      <id>github</id>
      <username>GH_KULLANICI_ADI</username>
      <password>GH_TOKEN_DEGERI</password>
    </server>
  </servers>
</settings>

Paketi yayımlamak için:

mvn deploy

Paketi tüketmek için pom.xml içine repository tanımı eklemek gerekiyor:

<repositories>
  <repository>
    <id>github</id>
    <url>https://maven.pkg.github.com/ORGANIZASYON/REPOSITORY</url>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>

Docker Image’larını GitHub Container Registry’de Saklamak

GitHub’ın Docker registry’si ghcr.io adresiyle hizmet veriyor. Eski docker.pkg.github.com adresi deprecated durumda, artık ghcr.io kullanın.

Image build ve push işlemi oldukça basit:

# Image'ı build et
docker build -t ghcr.io/ORGANIZASYON/uygulama-adi:1.0.0 .

# Registry'ye login ol
echo $GH_TOKEN | docker login ghcr.io -u $GH_USER --password-stdin

# Image'ı gönder
docker push ghcr.io/ORGANIZASYON/uygulama-adi:1.0.0

# Latest tag'ini de push et
docker tag ghcr.io/ORGANIZASYON/uygulama-adi:1.0.0 ghcr.io/ORGANIZASYON/uygulama-adi:latest
docker push ghcr.io/ORGANIZASYON/uygulama-adi:latest

Başka bir makineden bu image’ı çekmek için:

echo $GH_TOKEN | docker login ghcr.io -u $GH_USER --password-stdin
docker pull ghcr.io/ORGANIZASYON/uygulama-adi:1.0.0

Kubernetes ortamında bu image’ı kullanmak istiyorsanız bir imagePullSecret oluşturmanız gerekiyor:

kubectl create secret docker-registry ghcr-secret 
  --docker-server=ghcr.io 
  --docker-username=GH_KULLANICI_ADI 
  --docker-password=GH_TOKEN_DEGERI 
  [email protected] 
  -n uygulama-namespace

GitHub Actions ile Tam Otomatik Pipeline

El ile publish yapmak geliştirme ortamında kabul edilebilir ama üretimde CI/CD pipeline’ı olmadan olmaz. İşte npm paketi için tam bir GitHub Actions workflow’u:

name: Paket Yayimla

on:
  release:
    types: [created]
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Kodu cek
        uses: actions/checkout@v4

      - name: Node.js kur
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://npm.pkg.github.com'
          scope: '@sirket'

      - name: Bagimliliklari yukle
        run: npm ci

      - name: Testleri calistir
        run: npm test

      - name: Paketi yayimla
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Burada kritik nokta permissions bloğu. packages: write olmadan workflow paketi yayımlayamaz. GITHUB_TOKEN ise GitHub Actions tarafından otomatik sağlanıyor, ayrıca bir secret tanımlamanıza gerek yok.

Docker image için benzer bir workflow:

name: Docker Image Olustur ve Yayimla

on:
  push:
    branches: [main]
    tags: ['v*.*.*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Kodu cek
        uses: actions/checkout@v4

      - name: Docker Buildx kur
        uses: docker/setup-buildx-action@v3

      - name: GHCR'ye login ol
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Metadata olustur
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}

      - name: Image olustur ve push et
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Bu workflow main branch’e her push’ta ve v..* formatındaki tag’lerde çalışır. docker/metadata-action sayesinde tag yönetimi otomatik hale gelir.

Paket Görünürlüğünü ve Erişim Kontrolünü Yönetmek

GitHub Packages’ta paket görünürlüğü varsayılan olarak repository’nin görünürlüğünü miras alır. Private repository’den yayımlanan paket otomatik olarak private olur.

Container image’ları için görünürlüğü sonradan değiştirmek mümkün. GitHub > Packages > Paket Adı > Package settings yolunu izleyin. Burada şunları yapabilirsiniz:

  • Visibility: Public / Private arasında geçiş
  • Manage Actions access: Hangi repository’lerin bu pakete erişebileceğini belirleme
  • Manage access: Kullanıcı ve ekip bazlı erişim tanımlama

Organizasyon bazlı fine-grained erişim için şu akışı öneriyorum:

  • Paket sahipliğini bireysel kullanıcı yerine organizasyon altında tutun
  • Organizasyon içinde ekipler oluşturun (backend-team, frontend-team gibi)
  • Paket erişimini bu ekiplere tanımlayın, bireysel kullanıcılara değil

Bu sayede biri ayrıldığında tek tek paket erişimlerini güncellemenize gerek kalmaz.

Gerçek Dünya Senaryosu: Monorepo’da Çoklu Paket Yönetimi

Birden fazla paketi tek bir repository’den yönetiyorsanız (monorepo) işler biraz karmaşıklaşabilir. Pratik bir örnek verelim.

Diyelim ki şu yapıda bir monorepo’nuz var:

packages/
  api-client/
    package.json  (@sirket/api-client)
  ui-components/
    package.json  (@sirket/ui-components)
  shared-utils/
    package.json  (@sirket/shared-utils)

Her paketin kendi package.json dosyasında publishConfig tanımlı olacak. Workflow’da ise matrix strategy kullanmak mantıklı:

name: Monorepo Paket Yayimla

on:
  push:
    tags: ['v*']

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    strategy:
      matrix:
        package: [api-client, ui-components, shared-utils]

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://npm.pkg.github.com'

      - name: Paketi yayimla
        working-directory: packages/${{ matrix.package }}
        run: |
          npm ci
          npm test
          npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Bu yaklaşım her paketi paralel olarak test edip yayımlar. Bir paket başarısız olursa diğerleri etkilenmez.

Sık Karşılaşılan Hatalar ve Çözümleri

“Package not found” hatası private repo erişiminde

Private bir repository’nin paketine erişmeye çalışırken bu hatayla karşılaşıyorsanız, token’ınızda repo scope’u eksik olabilir. Ayrıca tüketen repository’nin Packages sayfasından “Manage Actions access” bölümüne gidip erişim izni verdiğinizden emin olun.

npm publish sırasında “unauthorized” hatası

Scope ile organizasyon adının birebir eşleşmesini kontrol edin. @Sirket ile @sirket farklıdır, büyük-küçük harf duyarlıdır.

Docker push’ta “denied: installation not allowed to Write organization package”

Workflow’da permissions: packages: write bloğunun eksik olduğu durumlarda bu hata gelir. Repository Settings > Actions > General > Workflow permissions bölümünden “Read and write permissions” seçeneğini de açmanız gerekebilir.

Eski package versiyonlarının birikmesi

GitHub Packages, paket versiyonlarını otomatik olarak silmez. Depolama limitine yaklaşıyorsanız eski versiyonları temizlemek için GitHub API’sini kullanabilirsiniz:

# Belirli bir paketteki eski versiyonları listele
gh api 
  -H "Accept: application/vnd.github+json" 
  /orgs/ORGANIZASYON/packages/npm/PAKET_ADI/versions 
  | jq '.[].id'

# Belirli bir versiyon ID'sini sil
gh api 
  --method DELETE 
  -H "Accept: application/vnd.github+json" 
  /orgs/ORGANIZASYON/packages/npm/PAKET_ADI/versions/VERSION_ID

Bunu otomatikleştirmek için bir cleanup workflow yazabilirsiniz, son 5 versiyonu koruyup gerisini silebilirsiniz mesela.

Dependabot ile Paket Güncellemelerini Otomatikleştirme

GitHub Packages’ta barındırdığınız paketlerin tüketici projelerinde güncel tutulması için Dependabot konfigürasyonuna registry tanımı ekleyebilirsiniz:

# .github/dependabot.yml
version: 2
registries:
  github-packages:
    type: npm-registry
    url: https://npm.pkg.github.com
    token: ${{ secrets.DEPENDABOT_TOKEN }}

updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    registries:
      - github-packages

Burada DEPENDABOT_TOKEN adında bir repository secret oluşturmanız gerekiyor. GITHUB_TOKEN Dependabot context’inde beklendiği gibi çalışmıyor, ayrı bir PAT almanız şart.

Sonuç

GitHub Packages, özellikle zaten GitHub ekosisteminde olan ekipler için düşük kurulum maliyetiyle ciddi bir değer sunuyor. npm, Maven ve Docker ihtiyaçlarınızı tek bir platformda karşılıyor, erişim kontrolünü repository izin sistemiyle entegre ediyor ve GitHub Actions ile neredeyse sıfır konfigürasyonla pipeline kurmanıza olanak tanıyor.

Büyük ölçekli ya da çok karmaşık erişim gereksinimleri olan ortamlar için Nexus veya Artifactory hala daha iyi alternatifler olabilir. Ama on kişilik bir ekip, birkaç iç paket ve halihazırda GitHub kullanımı varsa, GitHub Packages makul bir seçim. Ayrı bir servis kurup yönetmek yerine bu kadar zamanı başka yere harcamak daha verimli.

Deneyimlerime göre en çok değer katan kısım Docker tarafı oluyor: ghcr.io üzerinde private image barındırmak, Kubernetes secretları ile entegre etmek ve GitHub Actions’tan sıfır ek konfigürasyonla push işlemi yapmak gerçekten kullanışlı. npm ve Maven entegrasyonları ise biraz daha fazla ilk kurulum gerektiriyor ama bir kez oturtunca bakım neredeyse yok.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir