Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Yazı Tipi BoyutlandırıcıAa
  • Anasayfa
  • Teknoloji
    • Siber Güvenlik
    • Yapay Zeka
    • Donanım
    • Bilim
  • Yazılım
  • Savunma & İstihbarat
  • Oyun
  • Yaşam
    • Finans
    • Sinema
    • Dünyadan Haberler
  • İş Birliği
Okuma: GitLab ve Laravel ile VPS Üzerinde CI/CD: Push’tan Otomatik Dağıtım’a
Paylaş
Yazı Tipi BoyutlandırıcıAa
Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Ara
Bizi Takip Et
  • Hakkımızda
  • Gizlilik politikası
  • Tanıtım Yazısı ve Backlink Hizmeti
© 2026 Teknomers. All Rights Reserved.

Anasayfa » GitLab ve Laravel ile VPS Üzerinde CI/CD: Push’tan Otomatik Dağıtım’a

Yazılım

GitLab ve Laravel ile VPS Üzerinde CI/CD: Push’tan Otomatik Dağıtım’a

teknomers
Son güncelleme: 19 Haziran 2026 00:43
teknomers
Paylaş
Paylaş

Aşağıdaki kılavuz, Laravel uygulamalarını GitLab kullanarak CI/CD (Continuous Integration/Continuous Deployment) ile otomatik olarak dağıtmak isteyen yazılımcılar için hazırlanmıştır. GitLab Runner ile, her bir push (gönderim) işleminin otomatik olarak bağımlılıkları yüklemesi, kod kalitesini kontrol etmesi, testleri çalıştırması, Docker imajını oluşturması ve sunucuya dağıtması sağlanacaktır.


Ne Yapacağız

5 aşamadan oluşan bir pipeline oluşturacağız:

prepare → quality → test → build → deploy
AşamaAçıklama
prepareComposer ile bağımlılıkları yükler
qualityLaravel Pint ile kod stilini kontrol eder
testPest ile testleri çalıştırır
buildDocker imajını oluşturur ve Registry’ye yükler
deployVPS’ye SSH ile dağıtım yapar

Ön Gereksinimler

  • GitLab hesabı (gitlab.com ya da kendi sunucunuzda barındırma)
  • Ubuntu/Debian yüklü VPS ve Docker kurulumu
  • Pest ile yapılandırılmış bir Laravel projesi
  • VPS’ye SSH erişimi

Bölüm 1: GitLab Runner

GitLab kendiliğinden bir komut çalıştırmaz. Bunu yapan GitLab Runner‘dır; bu, herhangi bir makineye (VPS’nizde, bir sunucuya veya bilgisayarınıza) kurarak GitLab’dan çalıştırılacak işlerin beklenmesini sağlar.

Runner Nasıl Çalışır

┌──────────────┐          ┌──────────────────┐          ┌──────────────┐
│   GitLab     │◄─polling─│   GitLab Runner  │──executa─►│  Kodununuz  │
│  (sunucu)    │          │  (VPS'nizde)     │          │              │
└──────────────┘          └──────────────────┘          └──────────────┘

Runner, GitLab’ın her birkaç saniyede bir iş olup olmadığını kontrol etmesini sağlar. Bir iş mevcut olduğunda, alır, çalıştırır ve sonucu geri döner. Dilediğiniz kadar runner’ı farklı makinelerde kullanabilirsiniz ve GitLab bu işleri aralarında dağıtır.

Runner Çalıştırıcı Türleri

ÇalıştırıcıNasıl çalışırNe Zaman Kullanılır
ShellKomutları doğrudan sunucuda çalıştırırBasit, izolasyon yok
DockerHer bir işin içinde bir konteynerde çalışırTavsiye Edilir — tamamen izolasyon

Biz Docker çalıştırıcısını kullanacağız; bu sayede her iş bir konteyner içinde tanımladığınız imaj ile çalışacaktır. Bu, ortamın her zaman temiz ve tekrar edilebilir olmasını sağlar.

GitLab Runner’ı VPS üzerine Kurma

VPS’nize SSH ile bağlanın ve şu komutları çalıştırın:

# GitLab Runner'ı resmi deposunu ekle
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash

# Runner'ı kur
sudo apt-get install -y gitlab-runner

# Servisin çalıştığını kontrol et
sudo systemctl status gitlab-runner

💡 Runner, bir sistem servisi olarak (systemd) çalışır ve sunucu ile birlikte otomatik olarak başlatılır.

Runner’ı GitLab Üzerinde Kaydetme

Runner kurulduktan sonra, bunu GitLab projenizde kaydetmeniz gerekiyor. Bu aşama, ajansı repozitora bağlar.

GitLab’da: Settings > CI/CD > Runners > New project runner yolunu izleyin.

GitLab, bir kayıt tokeni oluşturacaktır. VPS’ye geri dönün ve şu komutu çalıştırın:

sudo gitlab-runner register

Komut birkaç etkileşimli soruyu yöneltecek:

Enter the GitLab instance URL:
> https://gitlab.com
Enter the registration token:
> glrt-xxxxxxxxxxxxxxxxxxxx
Enter a description for the runner:
> vps-production-runner
Enter tags for the runner (comma-separated):
> docker,laravel
Enter optional maintenance note for the runner:
Enter an executor:
> docker
Enter the default Docker image:
> php:8.4-cli-bookworm

Kayıttan sonraki Runner, GitLab arayüzünde “Çevrimiçi” olarak görünecektir.

Docker-in-Docker için Ek Yapılandırma

Docker build işlerinin konteynerler içinde çalışabilmesi için ek bir yapılandırma gereklidir. /etc/gitlab-runner/config.toml dosyasını düzenleyin:

[[runners]]
  name = "vps-production-runner"
  url = "https://gitlab.com"
  token = "glrt-xxxxxxxxxxxxxxxxxxxx"
  executor = "docker"

  [runners.docker]
    image = "php:8.4-cli-bookworm"
    privileged = true          # Docker-in-Docker için gerekli
    volumes = [
      "/certs/client",         # TLS sertifikaları için
      "/cache"
    ]

privileged = true seçeneği, iş konteynerinin host üzerindeki Docker daemon’una erişebilmesi için gereklidir.

Düzenleme yaptıktan sonra Runner’ı yeniden başlatın:

sudo systemctl restart gitlab-runner

⚠️ Güvenlik: privileged = true seçeneğini yalnızca Docker imajları oluşturan runnlerlar için kullanın. Test runnerları için privileged = false tutun.


Bölüm 2: .gitlab-ci.yml Dosyası

Pipeline mantığı, projenizin köküne yerleştirilecek tek bir dosya olan .gitlab-ci.yml‘de bulunur. GitLab, her gönderiminizde bu dosyayı okuyarak uygun pipeline’ı oluşturur.

Bu bölümde, dosyayı bölüm bölünerek oluşturacağız.

Global Yapılar

# .gitlab-ci.yml

default:
  interruptible: true
  retry:
    max: 1
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

workflow:
  rules:
    - if: $CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_COMMIT_BRANCH == "develop"
    - if: $CI_COMMIT_BRANCH =~ /^feature\//
    - when: never

Açıklama:

  • interruptible: true — yeni bir commit geldiğinde, mevcut çalışan pipeline iptal edilir. Bu, runner kaynaklarını tasarruf etmeyi ve sıralı yapıların atlanmasını önler.
  • retry — pipeline, altyapı hatalarını (runner’ın çökmesi, zaman aşımı gibi) göz ardı ederek, sadece kod hatalarına dayanarak yeniden dener.
  • workflow.rules — hangi durumların pipeline oluşturacağını belirler. Listelenmeyen dallardan (örneğin chore/fix-typo) hiçbir pipeline oluşturulmaz. Sonundaki when: never seçeneği, açıkça tanımlanmayan tüm durumları “yoksayarak” bir “else” niteliği taşır.

Aşamalar ve Değişkenler

stages:
  - prepare
  - quality
  - test
  - build
  - deploy

variables:
  PHP_VERSION: "8.4"
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache"
  COMPOSER_ALLOW_SUPERUSER: "1"
  COMPOSER_NO_INTERACTION: "1"

Aşama sırası, yürütme dizisini belirler. Bir aşama yalnızca tüm önceki aşamadaki işler başarıyla geçince başlar. Değişkenler, tüm işler için tutarlı bir ortam sağlamaktadır. COMPOSER_CACHE_DIR ayarı, Composer’ın önbelleğini proje içine yönlendirir. Bu, sonraki pipeline’lar arasında önbellek ayarlamayı kolaylaştırır.

Yeniden Kullanılabilir Şablonlar

# Tüm PHP işler için temel şablon
.php-env:
  image: "php:${PHP_VERSION}-cli-bookworm"
  before_script:
    - apt-get update -qq && apt-get install -y -qq git unzip libzip-dev libsqlite3-dev

# İşlerdeki tekrarları önlemek için kural ankrajları
.rules-ci:
  &rules-ci
  - if: $CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  - if: $CI_COMMIT_BRANCH == "main"
  - if: $CI_COMMIT_BRANCH == "develop"
  - if: $CI_COMMIT_BRANCH =~ /^feature\//

.deploy-ssh:
  stage: deploy
  image: alpine:3.21
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

GitLab CI’nin iki güçlü özelliği:

  • İş şablonları (ön ek .): gerçek iş yaratmaz, diğer işler için sadece temel olarak kullanılabilir.
  • YAML ankrajları (& tanımlar, * referans verir): aynı kuralları her işte yinelemekten kaçınır. Bir dal eklemeniz gerektiğinde, sadece bir yerde düzenlersiniz.

Bölüm 3: Pipeline İşleri

Aşama prepare: Bağımlılıkların Yüklenmesi

composer:install:
  extends: .php-env
  stage: prepare
  script:
    - cp .env.example .env
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - composer install --prefer-dist --no-progress --no-interaction
    - php artisan key:generate --ansi
  artifacts:
    paths:
      - vendor/
      - .env
    expire_in: 2 hours
  cache:
    key: composer-$CI_COMMIT_REF_SLUG
    paths:
      - .composer-cache/
  rules:
    *rules-ci

Bu iş, tüm işlerin temelidir. Bağımlılıkları yükler ve sonraki tüm işlere sunar.

artifacts mekanizması:

  • vendor/ dizinleri ve .env dosyası, işin sonunda “paketlenir” ve GitLab’da geçici olarak saklanır.
  • Herhangi bir iş, needs: [{job: composer:install, artifacts: true}] tanımladığında, bu dosyaları otomatik olarak alır; yeniden indirme gerektirmez.
  • expire_in: 2 hours otomatik temizleme garantisi sağlar.

cache, artifacts’tan farklıdır: farklı pipeline’lar arasında kalıcıdır (sadece aynı pipeline’ın işleri arasında değil). Bu, sonraki build’lerin hızını artırır çünkü Composer, her seferinde internetteki paketleri indirmek zorunda kalmaz, yalnızca composer.lock‘da bir değişiklik olup olmadığını kontrol eder.


Aşama quality: Pint ile Stil Kontrolü

pint:
  extends: .php-env
  stage: quality
  needs:
    - job: composer:install
      artifacts: true
  script:
    - vendor/bin/pint --test
  rules:
    *rules-ci

Laravel Pint, Laravel’in resmi kod formatlayıcısıdır; PHP-CS-Fixer’a dayanmaktadır. --test bayrağı, dosyaların değiştirilmediğini kontrol eder, yalnızca pint.json dosyasındaki (veya varsayılan olarak laravel presetini kullanan) kurallara uygun olup olmadığını kontrol eder.

Bazı dosyalar kötü formatlandığında, Pint sıfır olmayan bir çıkış kodu döndürür, bu iş başarısız olur ve pipeline durur, testler çalıştırılmadan önce. Bu, geliştiriciye hızlı bir geri bildirim sağlar.

💡 Düzenleyicinizi dosyayı kaydettiğinizde Pint’i çalışacak şekilde ayarlayın. CI bir güvenlik ağıdır, ana iş akışınız olmamalıdır.


Aşama test: Otomatik Testler ile Pest

pest:
  extends: .php-env
  stage: test
  needs:
    - job: composer:install
      artifacts: true
  script:
    - php artisan test --compact
  rules:
    *rules-ci

Pest, PHP için şık bir test çerçevesidir. php artisan test ise Laravel için rahat bir uygulayıcıdır.

--compact bayrağı, sonuçları sıkıştırılmış bir biçimde gösterir; bu, CI logları için idealdir, burada nelerin geçtiğini ve nelerin başarısız olduğunu hızlıca tespit etmek istersiniz.

needs hakkında: işlerden bağımsız bağımlılıkları tanımlamak için kullanılır, dolayısıyla pint ve pest paralel olarak çalışabilir (her ikisi de yalnızca composer:install‘a bağlıdır). test aşaması sadece quality aşaması tamamlandığında başlar, fakat aynı aşama içinde, bağımı olmayan işler aynı anda çalışır.


Aşama build: Docker İmajı Oluşturma ve Yayınlama

.docker-publish:
  stage: build
  image: docker:27.4.0-cli
  services:
    - docker:27.4.0-dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - |
      export IMAGE="${CI_REGISTRY_IMAGE}:${DOCKER_IMAGE_TAG}"
      export IMAGE_SHA="${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
      docker build -t "${IMAGE}" -t "${IMAGE_SHA}" .
      docker push "${IMAGE}"
      docker push "${IMAGE_SHA}"
      echo "✓ Yayınlanan: ${IMAGE}"

docker:build:develop:
  extends: .docker-publish
  variables:
    DOCKER_IMAGE_TAG: develop
  needs:
    - job: pest
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

docker:build:main:
  extends: .docker-publish
  variables:
    DOCKER_IMAGE_TAG: latest
  needs:
    - job: pest
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

docker:build:release:
  stage: build
  image: docker:27.4.0-cli
  services:
    - docker:27.4.0-dind
  needs:
    - job: pest
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - |
      export RELEASE_VERSION="${CI_COMMIT_TAG#v}"
      export IMAGE="${CI_REGISTRY_IMAGE}:${RELEASE_VERSION}"
      export IMAGE_SHA="${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
      docker build -t "${IMAGE}" -t "${CI_REGISTRY_IMAGE}:latest" -t "${IMAGE_SHA}" .
      docker push "${IMAGE}"
      docker push "${CI_REGISTRY_IMAGE}:latest"
      docker push "${IMAGE_SHA}"
      echo "✓ Yayınlanan sürüm ${RELEASE_VERSION}"
  rules:
    - if: $CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/

GitLab’in otomatik değişkenlarının analizi:

Değişkenİçerik
$CI_REGISTRYGitLab Container Registry URL’si
$CI_REGISTRY_USERKimlik doğrulama için kullanıcı (otomatik)
$CI_REGISTRY_PASSWORDKimlik doğrulama için şifre (otomatik)
$CI_REGISTRY_IMAGEİmajın tam yolu (ör. registry.gitlab.com/grup/proje)
$CI_COMMIT_SHORT_SHACommit hash’inin ilk 8 karakteri
$CI_COMMIT_TAGPipelines’i tetikleyen Git etiketi (ör. v1.4.2)

Commit SHA’sı ile etiketlemenin önemi:

registry.gitlab.com/seu-grupo/api:latest     ← mevcut üretim versiyonu
registry.gitlab.com/seu-grupo/api:1.4.2      ← semantik versiyon
registry.gitlab.com/seu-grupo/api:a1b2c3d4   ← kesin commit

SHA etiketi ile hangi commit’in hangi imajı ürettiğini her zaman bilirsiniz. Bu, izlenebilirlik ve geri dönebilme içindir; yalnızca önceki commit’in SHA’sını bilmeniz yeterlidir ve sunucudaki etiketi değiştirebilirsiniz.

docker:27.4.0-dind (Docker-in-Docker) servisi, CI konteyneri içinde Docker komutları çalıştırmanızı sağlar. Bu nedenle, Runner’ın çalıştırıcısının privileged = true olması gerekir.


Aşama deploy: VPS’ye SSH ile Dağıtım

Bu, nihai aşamadır ve sihirin gerçekleştiği yerdir. İş, VPS’nize SSH ile bağlanır ve çalışan konteyneri günceller.

.deploy-ssh:
  stage: deploy
  image: alpine:3.21
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

deploy:develop:
  extends: .deploy-ssh
  script:
    - |
      ssh $SSH_USER@$SSH_HOST_DEVELOP "
        docker pull ${CI_REGISTRY_IMAGE}:develop
        docker stop laravel-api-develop || true
        docker rm laravel-api-develop || true
        docker run -d \
          --name laravel-api-develop \
          --restart unless-stopped \
          -p 8081:80 \
          --env-file /opt/apps/api-develop/.env \
          ${CI_REGISTRY_IMAGE}:develop
        docker image prune -f
      "
  environment:
    name: develop
    url: $DEPLOY_DEVELOP_URL
  needs:
    - job: docker:build:develop
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

deploy:production:
  extends: .deploy-ssh
  script:
    - |
      ssh $SSH_USER@$SSH_HOST_PRODUCTION "
        docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
        docker pull ${CI_REGISTRY_IMAGE}:latest
        docker stop laravel-api || true
        docker rm laravel-api || true
        docker run -d \
          --name laravel-api \
          --restart unless-stopped \
          -p 8080:80 \
          --env-file /opt/apps/api/.env \
          ${CI_REGISTRY_IMAGE}:latest
        docker image prune -f
      "
  environment:
    name: production
    url: $DEPLOY_PRODUCTION_URL
  when: manual
  needs:
    - job: docker:build:main
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Deploy scriptini analiz etme:

  1. docker pull — Sunucuya en son imajı indirir.
  2. docker stop + docker rm — mevcut konteyneri durdurur ve siler. || true kullanımı, konteyner mevcut olmadığında script’in başarısız olmasını engeller (ilk çalıştırma).
  3. docker run — yeni konteyneri üretim yapılandırmalarıyla başlatır. --env-file seçeneği, sunucudaki bir .env dosyasına işaret eder; asla üretim kimlik bilgilerini repozitora taşımayın.
  4. docker image prune -f — kullanılmayan eski imajları temizler, böylece VPS disk alanı dolmaz.

when: manual üretim dağıtımı için güvenlik nedeniyle kasıtlı bir karardır: üretim dağıtımı otomatik olarak gerçekleşmez. İş, GitLab arayüzünde onay bekler, bir geliştirici veya teknoloji lideri “Oynat” butonuna tıklamak zorundadır.


Bölüm 4: SSH Anahtarlarının Yapılandırılması

Deploy’un çalışması için, Runner’ın VPS’ye parola olmadan SSH ile kimlik doğrulaması yapması gerekir. Bunu GitLab’ın değişkenlerini kullanarak güvenli bir şekilde ayarlayacağız.

Anahtar Çifti Oluşturma

Yerel makinenizde (veya güvenli bir yerde), CI için ayrılmış bir anahtar çifti oluşturun:

ssh-keygen -t ed25519 -C "gitlab-ci-deploy" -f ~/.ssh/gitlab_ci_deploy -N ""

Bu, iki dosya oluşturur:

  • ~/.ssh/gitlab_ci_deploy — özel anahtar (GitLab’a gidecek)
  • ~/.ssh/gitlab_ci_deploy.pub — genel anahtar (sunucuya gidecek)

Genel Anahtarı Sunucuya Ekleme

# Genel anahtarın içeriğini kopyalayın
cat ~/.ssh/gitlab_ci_deploy.pub

# Sunucuya, dağıtım kullanıcısının authorized_keys dosyasına ekleyin
echo "GENEL_ANAHTAR_İÇERİĞİ" >> ~/.ssh/authorized_keys

Known Hosts’a Ulaşma

# Yerel makinenizde, sunucunun parmak izini alın
ssh-keyscan -H SUNUCU_IP_DA_VPS

Çıktıyı kopyalayın (şöyle görünmelidir: |1|abc123...|ssh-ed25519 AAAA...).

GitLab’daki Değişkenleri Yapılandırma

Settings > CI/CD > Variables gidin ve aşağıdakileri ekleyin:

DeğişkenDeğerTürKorunmuş
SSH_PRIVATE_KEYgitlab_ci_deploy içeriğiFile✅ Evet
SSH_KNOWN_HOSTSssh-keyscan çıktısıVariable✅ Evet
SSH_USERVPS’deki SSH kullanıcısı (örneğin: ubuntu)Variable✅ Evet
SSH_HOST_PRODUCTIONVPS’nin IP veya host adıVariable✅ Evet
SSH_HOST_DEVELOPGeliştirme sunucusunun IP veya host adıVariable✅ Evet
DEPLOY_PRODUCTION_URLÜretimdeki API URL’siVariableHayır
DEPLOY_DEVELOP_URLGeliştirme ortamının URL’siVariableHayır

⚠️ Herhangi bir hassas değişkeni Masked ve Protected olarak işaretleyin. Masked, değerin günlüklerde görünmesini engeller. Protected, kullanımı korunmuş dallara ve etiketlere kısıtlar.


Bölüm 5: Multi-Stage Dockerfile

Dockerfile, üretim imajları için ince ve güvenli olma amacıyla multi-stage build standardını kullanır:

# syntax=docker/dockerfile:1

# ──────────────────────────────────────────────
# Aşama 1: Bağımlılıkları Composer ile yükler
# ──────────────────────────────────────────────
FROM composer:2 AS vendor

WORKDIR /app

COPY composer.json composer.lock ./

RUN composer install \
    --no-dev \
    --no-interaction \
    --no-scripts \
    --prefer-dist \
    --optimize-autoloader

COPY app ./app
COPY bootstrap ./bootstrap
COPY config ./config
COPY database ./database
COPY routes ./routes
COPY artisan ./artisan

RUN composer dump-autoload --optimize --classmap-authoritative --no-dev

# ──────────────────────────────────────────────
# Aşama 2: Final çalışma imajı
# ──────────────────────────────────────────────
FROM php:8.4-fpm-bookworm AS runtime

RUN apt-get update \
    && apt-get install -y --no-install-recommends nginx supervisor libzip-dev libpng-dev \
    libonig-dev libxml2-dev libpq-dev curl \
    && docker-php-ext-install bcmath opcache pdo_mysql pdo_pgsql zip \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /var/www/html

# Sadece önceden oluşturulmuş vendor dizinini kopyalar
COPY --from=vendor /app/vendor ./vendor

COPY app ./app
COPY bootstrap ./bootstrap
COPY config ./config
COPY database ./database
COPY public ./public
COPY resources ./resources
COPY routes ./routes
COPY artisan ./artisan

COPY docker/nginx/default.conf /etc/nginx/sites-available/default
COPY docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY docker/php/php.ini /usr/local/etc/php/conf.d/php.ini

RUN mkdir -p storage/framework/{cache,sessions,views} storage/logs bootstrap/cache \
    && chown -R www-data:www-data storage bootstrap/cache \
    && chmod -R ug+rwx storage bootstrap/cache

COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

EXPOSE 80

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

Neden iki aşama?

  • vendor aşaması, Composer’ın resmi imajını kullanır (git, unzip ve PHP paketlerini yüklemek için gerekli her şeyi içerir) ve optimize edilmiş vendor/ dizinini oluşturur.
  • runtime aşaması, temiz bir PHP imajı ile başlar ve yalnızca önceden oluşturulmuş olan vendor/ dizinini kopyalar.
  • Son imaj, Composer, git veya herhangi bir build aracını içermez. Bu, imajın boyutunu küçültür ve üretim için saldırı yüzeyini azaltır.

Supervisor, aynı konteynerde iki süreci yönetir: PHP-FPM (PHP isteklerini işler) ve Nginx (HTTP isteklerini alır ve PHP-FPM’e yönlendirir). Stateless bir API için, bu basit ve etkili bir yaklaşımdır.


Bölüm 6: VPS’yi Dağıtıma Hazırlama

Direktör Yapısı

Sunucuda dizin yapısını oluşturun:

sudo mkdir -p /opt/apps/api
sudo chown -R $USER:$USER /opt/apps/api

Üretim için .env Dosyası

Sunucuda doğrudan .env dosyasını oluşturun:

nano /opt/apps/api/.env
APP_NAME="API Adı"
APP_ENV=production
APP_KEY=base64:SİFRE
APP_DEBUG=false
APP_URL=https://api.domeniniz.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=api_üretim
DB_USERNAME=kullanici
DB_PASSWORD=super_gizli_sifre

# ... diğer değişkenler

🔒 Bu dosya asla repozitora gitmemelidir. Üretim sırlarını içerir ve yalnızca sunucuda mevcut olmalıdır.

Registry’de Docker’ı Kimlik Doğrulama

Üretim sunucunuzda, gizli bir imajı docker pull yapabilmesi için GitLab Registry’ne Docker’ı kimlik doğrulaması yapın:

docker login registry.gitlab.com

Ya da, etkileşimli şifre olmadan otomatik yapmak için, GitLab’da bir Deploy Token oluşturun (Settings > Repository > Deploy tokens) ile read_registry iznine sahip olun ve şu komutu kullanın:

docker login registry.gitlab.com -u KULLANICI_ADI -p PAROLA

Docker kimlik bilgileri ~/.docker/config.json altında kaydedilir, bu nedenle gelecekteki docker pull çağrıları, manuel kimlik doğrulaması olmaksızın çalışacaktır.


Tam .gitlab-ci.yml Dosyası

# .gitlab-ci.yml

default:
  interruptible: true
  retry:
    max: 1
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

workflow:
  rules:
    - if: $CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_COMMIT_BRANCH == "develop"
    - if: $CI_COMMIT_BRANCH =~ /^feature\//
    - when: never

stages:
  - prepare
  - quality
  - test
  - build
  - deploy

variables:
  PHP_VERSION: "8.4"
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache"
  COMPOSER_ALLOW_SUPERUSER: "1"
  COMPOSER_NO_INTERACTION: "1"

# ── Şablonlar ──────────────────────────────────────────────────────────────── 

.php-env:
  image: "php:${PHP_VERSION}-cli-bookworm"
  before_script:
    - apt-get update -qq && apt-get install -y -qq git unzip libzip-dev libsqlite3-dev

.rules-ci:
  &rules-ci
  - if: $CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  - if: $CI_COMMIT_BRANCH == "main"
  - if: $CI_COMMIT_BRANCH == "develop"
  - if: $CI_COMMIT_BRANCH =~ /^feature\//

.deploy-ssh:
  stage: deploy
  image: alpine:3.21
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

# ── Aşama: prepare ────────────────────────────────────────────────────────────  

composer:install:
  extends: .php-env
  stage: prepare
  script:
    - cp .env.example .env
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - composer install --prefer-dist --no-progress --no-interaction
    - php artisan key:generate --ansi
  artifacts:
    paths:
      - vendor/
      - .env
    expire_in: 2 hours
  cache:
    key: composer-$CI_COMMIT_REF_SLUG
    paths:
      - .composer-cache/
  rules:
    *rules-ci

# ── Aşama: quality ──────────────────────────────────────────────────────────── 

pint:
  extends: .php-env
  stage: quality
  needs:
    - job: composer:install
      artifacts: true
  script:
    - vendor/bin/pint --test
  rules:
    *rules-ci

# ── Aşama: test ─────────────────────────────────────────────────────────────── 

pest:
  extends: .php-env
  stage: test
  needs:
    - job: composer:install
      artifacts: true
  script:
    - php artisan test --compact
  rules:
    *rules-ci

# ── Aşama: build ────────────────────────────────────────────────────────────── 

.docker-publish:
  stage: build
  image: docker:27.4.0-cli
  services:
    - docker:27.4.0-dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - |
      export IMAGE="${CI_REGISTRY_IMAGE}:${DOCKER_IMAGE_TAG}"
      export IMAGE_SHA="${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
      docker build -t "${IMAGE}" -t "${IMAGE_SHA}" .
      docker push "${IMAGE}"
      docker push "${IMAGE_SHA}"
      echo "✓ Yayınlanan: ${IMAGE}"

docker:build:develop:
  extends: .docker-publish
  variables:
    DOCKER_IMAGE_TAG: develop
  needs:
    - job: pest
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

docker:build:main:
  extends: .docker-publish
  variables:
    DOCKER_IMAGE_TAG: latest
  needs:
    - job: pest
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

docker:build:release:
  stage: build
  image: docker:27.4.0-cli
  services:
    - docker:27.4.0-dind
  needs:
    - job: pest
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - |
      export RELEASE_VERSION="${CI_COMMIT_TAG#v}"
      export IMAGE="${CI_REGISTRY_IMAGE}:${RELEASE_VERSION}"
      export IMAGE_SHA="${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
      docker build -t "${IMAGE}" -t "${CI_REGISTRY_IMAGE}:latest" -t "${IMAGE_SHA}" .
      docker push "${IMAGE}"
      docker push "${CI_REGISTRY_IMAGE}:latest"
      docker push "${IMAGE_SHA}"
      echo "✓ Yayınlanan sürüm ${RELEASE_VERSION}"
  rules:
    - if: $CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/

# ── Aşama: deploy ───────────────────────────────────────────────────────────── 

deploy:develop:
  extends: .deploy-ssh
  script:
    - |
      ssh $SSH_USER@$SSH_HOST_DEVELOP "
        docker pull ${CI_REGISTRY_IMAGE}:develop
        docker stop laravel-api-develop || true
        docker rm laravel-api-develop || true
        docker run -d \
          --name laravel-api-develop \
          --restart unless-stopped \
          -p 8081:80 \
          --env-file /opt/apps/api-develop/.env \
          ${CI_REGISTRY_IMAGE}:develop
        docker image prune -f
      "
  environment:
    name: develop
    url: $DEPLOY_DEVELOP_URL
  needs:
    - job: docker:build:develop
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

deploy:production:
  extends: .deploy-ssh
  script:
    - |
      ssh $SSH_USER@$SSH_HOST_PRODUCTION "
        docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
        docker pull ${CI_REGISTRY_IMAGE}:latest
        docker stop laravel-api || true
        docker rm laravel-api || true
        docker run -d \
          --name laravel-api \
          --restart unless-stopped \
          -p 8080:80 \
          --env-file /opt/apps/api/.env \
          ${CI_REGISTRY_IMAGE}:latest
        docker image prune -f
      "
  environment:
    name: production
    url: $DEPLOY_PRODUCTION_URL
  when: manual
  needs:
    - job: docker:build:main
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Tam Akış Diyagramı

Push na branch main
        │
        ▼
┌────────────────────┐
│  composer:install  │ ← Bağımlılıkları yükler, vendor/ ve .env oluşturur
└──────────┬─────────┘
           │ artifacts
      ┌────┴────┐
      ▼         ▼
  ┌───────┐ ┌──────┐
  │ pint  │ │ pest │ ← Paralel olarak çalışır (her ikisi de composer:install'a bağlı)
  └───────┘ └──┬───┘
               │ geçti
               ▼
   ┌────────────────────┐
   │  docker:build:main │ ← build + push :latest ve :sha
   └──────────┬─────────┘
              │
              ▼
   ┌───────────────────┐
   │  deploy:production│ ← Manual onay bekler [▶ Play]
   │  SSH → VPS        │
   │  docker pull      │
   │  docker run       │
   └───────────────────┘

Kontrol Listesi

İlk kez pipeline’ı test etmeden önce, şunları kontrol edin:

  • [ ] GitLab Runner kurulu ve projeye kaydedildi
  • [ ] Çalıştırıcı, docker olarak yapılandırıldı ve privileged = true
  • [ ] SSH anahtar çifti oluşturuldu ve yapılandırıldı
  • [ ] Genel anahtar, VPS’deki authorized_keys dosyasına eklendi
  • [ ] GitLab’da CI/CD değişkenleri yapılandırıldı
  • [ ] Üretim için .env dosyası sunucuda manuel olarak oluşturuldu
  • [ ] VPS’de Docker, GitLab Registry’de kimlik doğrulaması yapıldı
  • [ ] /opt/apps/api dizini VPS’de oluşturuldu
  • [ ] Projede pint.json doğru bir şekilde ayarlandı (veya varsayılan laravel presetini kullanarak)
  • [ ] İlk gönderimden önce, Pest testleri yerel olarak geçiyor

Sonuç

Bu yapılandırma ile, repozitronuza her gönderim, kodun doğru formatlandığını, testlerin geçerli olduğunu ve istediğiniz zaman yeni sürümün sunucuya yalnızca bir tıklama ile teslim edilmesini sağlayan bir pipeline’ı tetikler.

Sonuç olarak, dağıtım konusunda kendinize güvenebilirsiniz. Artık manuel SSH yok, “bir şey bozulacak mı?” korkusu yok, artık Cuma günleri saat 17:00’de yüreğinizi ağızınıza getiren dağıtımlar yok.

Bu yapı, tek bir kişilik bir takım için yeterince basit ve projeyle birlikte büyüyecek kadar ölçeklenebilir; yeni ortamlar ekleyebilir, yeni statik analiz aşamaları (PHPStan gibi) ekleyebilir veya daha karmaşık bir orkestratöre geçebilirsiniz — tüm bunları yeniden yazmanıza gerek kalmadan.

Kaynak: Orijinal Makale

Contents
  • Ne Yapacağız
  • Ön Gereksinimler
  • Bölüm 1: GitLab Runner
    • Runner Nasıl Çalışır
    • Runner Çalıştırıcı Türleri
    • GitLab Runner’ı VPS üzerine Kurma
    • Runner’ı GitLab Üzerinde Kaydetme
    • Docker-in-Docker için Ek Yapılandırma
  • Bölüm 2: .gitlab-ci.yml Dosyası
    • Global Yapılar
    • Aşamalar ve Değişkenler
    • Yeniden Kullanılabilir Şablonlar
  • Bölüm 3: Pipeline İşleri
    • Aşama prepare: Bağımlılıkların Yüklenmesi
    • Aşama quality: Pint ile Stil Kontrolü
    • Aşama test: Otomatik Testler ile Pest
    • Aşama build: Docker İmajı Oluşturma ve Yayınlama
    • Aşama deploy: VPS’ye SSH ile Dağıtım
  • Bölüm 4: SSH Anahtarlarının Yapılandırılması
    • Anahtar Çifti Oluşturma
    • Genel Anahtarı Sunucuya Ekleme
    • Known Hosts’a Ulaşma
    • GitLab’daki Değişkenleri Yapılandırma
  • Bölüm 5: Multi-Stage Dockerfile
  • Bölüm 6: VPS’yi Dağıtıma Hazırlama
    • Direktör Yapısı
    • Üretim için .env Dosyası
    • Registry’de Docker’ı Kimlik Doğrulama
  • Tam .gitlab-ci.yml Dosyası
  • Tam Akış Diyagramı
  • Kontrol Listesi
  • Sonuç
Laravel İzin Sertleştirme Scripti – DEV Community
Android 11’in Çıkış Tarihi Sızdı
Kismet Yazılımı Nasıl Kurulur ve Kullanılır?
API Geçidinin Konfigürasyonunu Yedekleme — Bağlantı Başına, Geri Yükleme Güvenli
Shippo API için Modern Bir PHP/Laravel SDK: Gönderim Sürecinin Kontrol Altına Alınması
Bu Makaleyi Paylaş
Facebook Bağlantıyı Kopyala Yazdır
Paylaş
Önceki Makale Baseten Şirketi Dev Bir Adım Attı: Yatırım Turu Tamamlandı
Sonraki Makale Amazon Erken Prime Günü indiriminde 8-portlu 2.5G Ethernet anahtarında %62 tasarruf edin!

Sanal Medya

FacebookBeğen
452Takip Et
PinterestSabitle
237Takip Et

Son Eklenenler

Telegram’ın Hindistan’daki Yasağı VPN ve Rakip Uygulamalara Talebi Artırdı
Genel
Elastic, CRV destekli DeductiveAI’yi 85M$’ye alıyor
Yapay Zeka
Trump, Apple’ın Intel ile çip üretimi için anlaştığını açıkladı
Donanım

MapTap, günlük bir coğrafya oyunu, benim yeni Wordle’ım oldu

Yeni Bir Eğlence: Coğrafya Oyunu MapTap ile Tanışın

Genel
Aimeos ile Beş Dakikada Çalışan Bir Laravel Mağazası
Yazılım
2026 Amazon Prime Günü: Breville ve Ninja Espresso Makinelerinde Büyük İndirimler!
Genel
//

Siber güvenlik, yapay zeka ve savunma sanayiinden; finans ve sinema dünyasına uzanan geniş bir yelpaze. Teknomers; teknoloji, strateji ve yazılım dünyasını sade bir dille sizlerle buluşturuyor.

Kurumsal

  • Hakkımızda
  • Gizlilik politikası
  • Tanıtım Yazısı ve Backlink Hizmeti

Kategoriler

  • Teknoloji
  • Oyun
  • Sinema
  • Siber Güvenlik
  • Bilim
  • Finans
  • Dünyadan Güncel Haberler

Populer

  • TV'de Ücretsiz İzlenebilen Şifresiz Erotik Kanallar (2025 Güncel Frekans Listesi)

  • The Last of Us PC Kontrolleri: Hızlı Silah Değiştirme ve Tüm Tuşlar (2025)

  • Hogwarts Legacy'de Odaklanma İksiri Nasıl Yapılır?

Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Bizi Takip Et
© 2026 Teknomers. All Rights Reserved.
Welcome Back!

Sign in to your account

Kullanıcı Adı veya E-posta Adresi
Şifre

Şifrenizi mi unuttunuz?