Belajar Symfony - Docker & Deployment CI/CD
Episode 23 of 27

Belajar Symfony - Docker & Deployment CI/CD

Membawa Symfony ke production dengan Docker: Dockerfile multi-stage, docker-compose untuk stack app + db + redis, menjalankan migrasi dan cache warmup saat deploy, serta pipeline GitHub Actions untuk testing, build image, dan deploy staging ke production.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah worker mode di episode 22, episode 23 menjawab pertanyaan operasional terbesar: bagaimana aplikasi ini sampai ke server dengan konsisten? Jawabannya: container image + pipeline. Image Docker menjamin lingkungan identik dari laptop sampai production; CI/CD mengotomasi pengujian dan deployment.

Mengapa ini penting? Karena "jalan di laptop saya" adalah lawan utama keandalan. Dengan image yang di-build sekali dan di-deploy ke mana pun, kalian menghilangkan seluruh kelas masalah konfigurasi — dan migrasi & cache warmup yang benar saat deploy mencegah downtime dan error post-deploy.

Dockerfile Multi-Stage

Satu file image dengan beberapa stage: composer (dependensi), build (kode + optimasi), dan runtime (hasil akhir yang ramping):

Dockerfile — multi-stage
FROM composer:2 AS composer
 
FROM php:8.4-cli AS build
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN apt-get update && apt-get install -y unzip git libpq-dev \
    && docker-php-ext-install pdo_pgsql intl opcache
WORKDIR /app
COPY composer.json composer.lock symfony.lock ./
RUN composer install --no-dev --optimize-autoloader --prefer-dist \
    --no-interaction --no-progress
COPY . .
RUN php bin/console cache:clear --env=prod --no-debug \
    && php bin/console cache:warmup --env=prod --no-debug
 
FROM dunglas/frankenphp:8.4 AS runtime
COPY --from=build /app /app
WORKDIR /app
ENV APP_ENV=prod APP_DEBUG=0
EXPOSE 80

Mengapa multi-stage? Stage composer & build menyimpan toolchain pengembangan; stage runtime hanya berisi hasil jadi — image lebih kecil, permukaan serangan lebih sempit. Perhatikan cache warmup di stage build: container runtime langsung berjalan dengan cache container yang hangat.

Docker Compose: Stack Lengkap

docker-compose.yml merangkai aplikasi + database + Redis:

docker-compose.yml
services:
  app:
    build: .
    ports:
      - "8080:80"
    env_file: .env.prod
    depends_on:
      - db
      - redis
 
  db:
    image: postgres:17
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
 
  redis:
    image: redis:7-alpine
 
volumes:
  db_data:

Env var secret diambil dari .env.prod (tidak di-commit) — seperti pola episode 16. Redis digunakan untuk cache/session/rate limiter shared (episode 14/20/24).

Menjalankan Migrasi Saat Deploy

Migrasi tidak boleh dieksekusi sembarangan. Pola yang benar saat deploy:

Migrate di production
docker compose run --rm app php bin/console doctrine:migrations:migrate \
    --env=prod --no-interaction --allow-no-migration

Aturan penting:

  1. Jalankan sebelum traffic baru masuk — aplikasi baru butuh skema baru.
  2. Satu kali saja — pastikan mekanisme deploy tidak mengeksekusi migrasi ganda (lock/state di DB).
  3. --no-interaction — dalam pipeline, tidak boleh menunggu input manual.
  4. Backward compatible — jika ada downtime-free (blue/green), migrasi harus backward-compatible.

Cache Warmup yang Benar

Setelah deploy, container baru harus hangat:

Warmup di production
docker compose run --rm app php bin/console cache:clear --env=prod --no-debug
docker compose run --rm app php bin/console cache:warmup --env=prod --no-debug

Biasakan memanggil warmup, bukan hanya clear — warmup membangun semua cache (routes, container, twig) di muka sehingga request pertama tidak menderita cold start. Pada image yang sudah di-warmup di stage build (Dockerfile di atas), langkah ini opsional — tetapi tetap berguna untuk memvalidasi container sebelum traffic masuk.

Pipeline GitHub Actions

CI/CD lengkap punya tiga tahap: test → build → deploy.

.github/workflows/deploy.yml
name: Deploy
 
on:
  push:
    branches: [main]
 
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: test
        ports: ["5432:5432"]
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          tools: composer
      - run: composer install --no-interaction
      - run: php bin/phpunit
      - run: composer audit
 
  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - name: Build & push image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: registry.example.com/myapp:${{ github.sha }}
          secrets: |
            "APP_SECRET=${{ secrets.APP_SECRET }}"
 
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy ke staging→production
        run: |
          # contoh: SSH ke server, tarik image, jalankan migrate + up
          ssh deploy@server "cd /srv/myapp && docker compose pull \
            && docker compose run --rm app php bin/console doctrine:migrations:migrate \
               --env=prod --no-interaction \
            && docker compose up -d"

Struktur yang perlu diperhatikan:

TahapTanggung jawab
testPHPUnit + composer audit (episode 17/18)
buildBuild image + push ke registry (tag = SHA commit)
deployTarik image baru, migrate, restart container

Secrets (episode 16) tersimpan di GitHub Secrets — tidak pernah di file workflow. composer audit di pipeline menjamin dependensi rentan tertangkap sebelum sampai production.

Strategi Deploy

StrategiDowntimeKompleksitasCocok untuk
Recreate (up -d)Beberapa detikRendahAplikasi kecil
Rolling updateMinimSedangOrkestrasi (Swarm/K8s)
Blue/GreenNolTinggiEnterprise, SLA ketat

Apapun strateginya, urutannya konsisten: test → build → migrate → warmup → switch traffic.

Warning

Jangan pernah meletakkan migrasi "di dalam" startup container (CMD/entrypoint). Beberapa instance bisa mengeksekusi migrasi bersamaan → race condition & skema korup. Jalankan migrasi sebagai langkah deploy eksplisit (seperti contoh di atas) atau gunakan lock berbasis DB.

Common Pitfalls

  • Image tanpa cache warmup — request pertama lambat; warmup di build stage.
  • Env var bocor di image — secret lewat build --secret/CI secrets, bukan ENV di Dockerfile.
  • Lupa volume untuk upload — storage user harus persistent volume, bukan di dalam container.
  • Migrasi di startup — race condition; jalankan eksplisit saat deploy.

Penutup

Pada episode 23 ini, kalian telah mempackaging dan mendeploy aplikasi.

Inti yang harus dibawa pulang:

  • Dockerfile multi-stage: composer → build (warmup) → runtime ramping.
  • Compose merangkai app + db + redis; secret via .env.prod.
  • Migrasi saat deploy: sekali, --no-interaction, sebelum traffic baru.
  • Warmup (bukan cuma clear) untuk menghindari cold start.
  • CI: test + composer audit → build image → migrate → deploy.

Di episode 24 selanjutnya kita menskalakan: Scaling & High Availability — stateless app, Redis sessions & cache, database read replicas, load balancing horizontal, dan load testing untuk tuning. Sampai jumpa di episode 24!

Belajar Symfony - Docker & Deployment CI/CD | Belajar Symfony