GitHub Actions ile Docker İmajı Otomatik Build ve Push
Her sabah ofise gelip “acaba gece build bozuldu mu?” diye endişeyle Jenkins’e bakmak zorunda kalmak… O dönemi hatırlıyorum. Sonra GitHub Actions ile tanıştım ve hayatım değişti. Özellikle Docker imajı build edip registry’e push etme sürecini otomatikleştirdiğimde, “neden bunu daha önce yapmadım?” diye kendime sordum. Bu yazıda, GitHub Actions kullanarak Docker imajlarını nasıl build edip push edeceğinizi, gerçek dünya senaryolarıyla ve bol bol kod örneğiyle anlatacağım.
Neden GitHub Actions + Docker?
Çoğu ekip şu döngüde sıkışıp kalıyor: geliştirici kodu yazar, lokal ortamda test eder, sonra “bende çalışıyor abi” der ve production’a çıkmak için manuel adımlar başlar. Docker bu problemi büyük ölçüde çözdü ama imaj build edip push etmek hala manuel bir süreçti. GitHub Actions bu süreci tamamen otomatize ediyor.
GitHub Actions’ın öne çıkan avantajları:
- Kodla birlikte yaşayan pipeline tanımları (YAML dosyaları repo’da)
- GitHub ile native entegrasyon (PR, branch, tag olaylarını dinleyebilme)
- Ücretsiz tier ile birçok küçük-orta proje için yeterli dakika
- Docker Hub, GHCR (GitHub Container Registry), AWS ECR, GCP Artifact Registry gibi her major registry ile entegrasyon
- Marketplace’te hazır action’ların bolluğu
Temel Yapı: Workflow Dosyası
GitHub Actions workflow’ları .github/workflows/ dizini altında YAML dosyaları olarak tanımlanır. Önce en basit haliyle başlayalım, sonra üzerine ekleyeceğiz.
mkdir -p .github/workflows
touch .github/workflows/docker-build-push.yml
İlk workflow dosyamız:
name: Docker Build and Push
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: kullaniciadiniz/uygulamaniz:latest
Bu en minimal hali. Ama “latest” tag’iyle çalışmak production’da ciddi sorunlara yol açar. Şimdi bunu düzeltelim.
Secret’ları Doğru Yapılandırmak
Workflow’a geçmeden önce secret’ları ayarlamak gerekiyor. GitHub repository sayfasında Settings > Secrets and variables > Actions yolunu izliyorsunuz.
Docker Hub için iki şey gerekiyor:
- DOCKERHUB_USERNAME: Docker Hub kullanıcı adınız
- DOCKERHUB_TOKEN: Docker Hub’dan oluşturduğunuz Access Token (şifrenizi değil, token kullanın!)
Docker Hub Access Token oluşturmak için: Hub.docker.com > Account Settings > Security > New Access Token.
GHCR (GitHub Container Registry) kullanıyorsanız işiniz daha kolay çünkü GITHUB_TOKEN otomatik olarak mevcut:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
Akıllı Tag Stratejisi
“latest” tag’i kullanmak neden sorunlu? Çünkü hangi imajın production’da çalıştığını bilemezsiniz. Bir şeyler bozulduğunda “hangi versiyona döneceğiz?” sorusu cevaplanamaz hale gelir. Doğru yaklaşım şu:
name: Docker Build and Push
on:
push:
branches:
- main
- develop
tags:
- 'v*.*.*'
pull_request:
branches:
- main
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-,format=short
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker/metadata-action bu işi inanılmaz kolaylaştırıyor. Yukarıdaki konfigürasyonla:
mainbranch’e push atıldığında:ghcr.io/orgadi/repo:maintag’i oluşurv1.2.3tag’i push edildiğinde:v1.2.3ve1.2tag’leri oluşur- PR açıldığında: imaj build edilir ama push edilmez (test amaçlı)
- Her commit için kısa SHA tag’i oluşur (örn:
sha-abc1234)
Build Cache ile Süreyi Düşürmek
GitHub Actions’da her workflow çalışması temiz bir ortamda başlar. Bu güzel bir şey ama Docker build için dezavantaj: her seferinde her layer yeniden build edilir. Büyük projelerde bu 10-15 dakikayı bulabiliyor.
Cache kullanmak için:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
type=gha (GitHub Actions Cache) kullanmak için docker/setup-buildx-action şart. Bu sayede layer cache’i GitHub Actions’ın kendi cache mekanizmasında tutuyor. İlk build uzun sürebilir ama sonraki build’ler dramatik biçimde kısalıyor.
Alternatif olarak registry cache de kullanılabilir:
cache-from: type=registry,ref=ghcr.io/orgadi/repo:buildcache
cache-to: type=registry,ref=ghcr.io/orgadi/repo:buildcache,mode=max
Multi-Platform Build (ARM + AMD64)
Apple Silicon Mac’lerin yaygınlaşmasıyla birlikte ARM imajlarına olan ihtiyaç arttı. Ayrıca AWS Graviton gibi ARM tabanlı sunucular maliyet avantajı sunuyor. GitHub Actions ile multi-platform build yapmak düşündüğünüzden kolay:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push multi-platform image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
QEMU, QEMU emülatörünü kuruyor ve farklı mimarileri simüle ediyor. Native ARM runner’ınız yoksa emülasyon yavaş olabilir ama küçük-orta imajlar için kabul edilebilir.
Gerçek Dünya Senaryosu: Node.js API Projesi
Şimdi gerçek bir senaryo üzerinden gidelim. Bir Node.js API projeniz var, staging ve production ortamlarınız var, ve şu kuralları uygulamak istiyorsunuz:
developbranch’e her push’ta staging registry’ye imaj göndermainbranch’e merge olduğunda production registry’ye gönder- Semantic versioning tag’i push edildiğinde production’a “release” imajı gönder
- Test geçmeden push yapma
name: CI/CD Pipeline
on:
push:
branches:
- main
- develop
tags:
- 'v*.*.*'
pull_request:
branches:
- main
- develop
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linting
run: npm run lint
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
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}}
type=sha,prefix=git-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILD_DATE=${{ github.event.repository.updated_at }}
GIT_COMMIT=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
needs: test satırı kritik. Bu sayede test job’u başarısız olursa build job’u hiç başlamıyor. Kırık imaj registry’ye gitmemiş oluyor.
Build Arguments ve Secrets
Dockerfile içine build zamanında değer geçirmek çok yaygın bir ihtiyaç. Özellikle versiyon numarası, git commit hash veya ortam bilgisi gibi şeyler için:
# Dockerfile örneği
FROM node:20-alpine
ARG BUILD_DATE
ARG GIT_COMMIT
ARG APP_VERSION=unknown
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.revision=$GIT_COMMIT
LABEL org.opencontainers.image.version=$APP_VERSION
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV APP_VERSION=$APP_VERSION
EXPOSE 3000
CMD ["node", "server.js"]
Workflow’da build-args kullanımı:
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args: |
BUILD_DATE=${{ github.event.repository.updated_at }}
GIT_COMMIT=${{ github.sha }}
APP_VERSION=${{ steps.meta.outputs.version }}
secrets: |
NPM_TOKEN=${{ secrets.NPM_TOKEN }}
Build sırasında secret kullanmak için secrets parametresi var. Bu secret’lar imaj içine gömülmüyor, sadece build zamanında kullanılıyor. Dockerfile’da şöyle kullanıyorsunuz:
RUN --mount=type=secret,id=NPM_TOKEN
NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN)
npm install --save-dev @ozel/paket
Güvenlik Taraması Eklemek
İmajı push etmeden önce güvenlik açığı taraması yapmak production ortamları için neredeyse zorunlu. Trivy ile bunu pipeline’a entegre etmek çok kolay:
security-scan:
needs: build-and-push
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
exit-code: '1' ayarı kritik. Bu sayede CRITICAL veya HIGH bir güvenlik açığı bulunursa pipeline başarısız oluyor. Sonuçlar GitHub’ın Security sekmesinde görünüyor, PR’larda warning olarak çıkıyor.
AWS ECR ile Entegrasyon
Bazı ekipler Docker Hub veya GHCR yerine AWS ECR kullanıyor. Bu durumda authentication biraz farklı işliyor:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push to ECR
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ steps.login-ecr.outputs.registry }}/uygulamaniz:latest
${{ steps.login-ecr.outputs.registry }}/uygulamaniz:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
AWS IAM için en az yetkili kullanıcı oluşturmayı unutmayın. ECR push için gereken minimum yetkiler:
- ecr:GetAuthorizationToken: ECR token almak için
- ecr:BatchCheckLayerAvailability: Layer kontrolü
- ecr:PutImage: İmaj push etmek
- ecr:InitiateLayerUpload: Layer upload başlatmak
- ecr:UploadLayerPart: Layer parçası yüklemek
- ecr:CompleteLayerUpload: Layer upload tamamlamak
Matrix Build ile Farklı Versiyonlar
Birden fazla Node.js veya Python versiyonu için imaj build etmek gerektiğinde matrix stratejisi kullanabilirsiniz:
jobs:
build-matrix:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['18', '20', '21']
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
build-args: |
NODE_VERSION=${{ matrix.node-version }}
tags: |
ghcr.io/${{ github.repository }}:node${{ matrix.node-version }}
ghcr.io/${{ github.repository }}:node${{ matrix.node-version }}-${{ github.sha }}
cache-from: type=gha,scope=node-${{ matrix.node-version }}
cache-to: type=gha,scope=node-${{ matrix.node-version }},mode=max
Matrix build’de cache scope’u belirtmek önemli. Aksi halde farklı versiyonların cache’leri birbirini eziyor.
Yaygın Sorunlar ve Çözümleri
Pratikte karşılaşılan sorunlar ve çözümleri:
Build çok uzun sürüyor:
cache-from/cache-toekleyin- Dockerfile’ınızı optimize edin (sık değişen COPY komutlarını sona alın)
- Multi-stage build kullanın
“denied: requested access to the resource is denied” hatası:
- Secret değerlerini kontrol edin
- GHCR için
packages: writepermission’ı ekleyin - Docker Hub için şifre yerine Access Token kullanın
“no space left on device” hatası:
docker/setup-buildx-action‘adriver-opts: image=moby/buildkit:latestekleyin- Büyük base image kullanıyorsanız runner’ın disk kapasitesini kontrol edin
PR’larda imaj push edilmek istenmiyor ama edilmesi gerekiyor:
if: github.event_name != 'pull_request'koşulunu kullanın- Ya da PR’lar için farklı bir registry veya tag yapısı belirleyin
Bildirimler ve Monitoring
Pipeline başarısız olduğunda haberdar olmak için Slack veya Teams entegrasyonu ekleyebilirsiniz:
- name: Slack Bildirimi
if: failure()
uses: slackapi/[email protected]
with:
payload: |
{
"text": "Docker build basarisiz oldu!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}* - Build BASARISIZnBranch: `${{ github.ref_name }}`nCommit: `${{ github.sha }}`n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Workflow'u Goruntule>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
Sonuç
GitHub Actions ile Docker build ve push sürecini otomatikleştirmek başlangıçta karmaşık görünebilir ama bir kez kurduğunuzda “nasıl bu olmadan çalışıyordum?” diyeceksiniz. Özetlemek gerekirse:
- “latest” yerine anlamlı tag stratejisi kullanın (semver, branch adı, git SHA kombinasyonu)
- Build cache eklemek build sürelerini dramatik biçimde düşürür
- Test job’u başarısız olursa build job’un çalışmamasını sağlayın (
needskullanın) - PR’larda build yapın ama push etmeyin, böylece kırık kod registry’ye gitmez
- Trivy veya benzeri bir araçla güvenlik taraması ekleyin
- Secret’lar için her zaman GitHub Secrets kullanın, workflow dosyasına asla hardcode etmeyin
Multi-platform build, matrix stratejisi ve güvenlik taraması gibi ileri seviye konulara ihtiyaç duyup duymadığınız ekip büyüklüğünüze ve projenizin kritikliğine göre değişir. Ama temel yapıyı doğru kurmak, üzerine her şeyi inşa etmeyi kolaylaştırıyor. Küçük başlayın, giderek iyileştirin. Bu işin püf noktası bu zaten.
