2026๋ Symfony์ Docker: ๊ฐ๋ฐ ํ๊ฒฝ๋ถํฐ ํ๋ก๋์ ๋ฐฐํฌ๊น์ง
2026๋ ์ ์ํ ์์ ํ Symfony Docker ์ํฌํ๋ก. FrankenPHP ๊ฐ๋ฐ ํ๊ฒฝ, ๋ฉํฐ ์คํ ์ด์ง ํ๋ก๋์ ์ด๋ฏธ์ง, ์์ปค ๋ชจ๋, ์ํธํ๋ ์ํฌ๋ฆฟ, ์ด๋ฏธ์ง ์ต์ ํ, ๊ทธ๋ฆฌ๊ณ ๋ง์ด๊ทธ๋ ์ด์ ์ ํฌํจํ ๋ฌด์ค๋จ ๋ฐฐํฌ๋ฅผ ๋ค๋ฃน๋๋ค.

์ด Symfony Docker ํํ ๋ฆฌ์ผ์ ์์ ํ ์ปจํ ์ด๋ ์ํฌํ๋ก๋ฅผ ๊ตฌ์ถํฉ๋๋ค. ์ฌํ ๊ฐ๋ฅํ ๋ก์ปฌ ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ๊ฒฌ๊ณ ํ๊ฒ ๋ค๋ฌ์ ํ๋ก๋์ ์ด๋ฏธ์ง๋ฅผ ๋ค๋ฃจ๋ฉฐ, ๋ ๊ฐ์ง ๋ชจ๋ FrankenPHP์ Symfony 7.4 LTS๋ฅผ ์ค์ฌ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. Symfony๋ฅผ ์ปจํ ์ด๋์์ ์คํํ๋ฉด ๋ ธํธ๋ถ๊ณผ ์๋ฒ ์ฌ์ด์ ๋ฐ์ํ๋ ๊ณ ์ ์ ์ธ ํ๊ฒฝ ๋ถ์ผ์น๊ฐ ์ฌ๋ผ์ง๊ณ , ๋งค ๋ฐฐํฌ๊ฐ ์ทจ์ฝํ ์์์ ์๋ฒ ๋ช ๋ น ์ํ์ค๋ฅผ ์คํํ๋ ๋์ ๋จ์ผ ๋ถ๋ณ ์ํฐํฉํธ๋ฅผ ๋ฐฐํฌํ๋ ์ผ๋ก ๋ฐ๋๋๋ค.
๊ณต์ Symfony Docker ํ ํ๋ฆฟ์ ์ด์ ๋ฐํ์์ผ๋ก FrankenPHP๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ ํต์ ์ธ Nginx์ PHP-FPM ์กฐํฉ์ ๋์ฒดํฉ๋๋ค. ๋จ์ผ ๋ฐ์ด๋๋ฆฌ๊ฐ HTTP/2์ HTTP/3๋ฅผ ์๋นํ๊ณ , ์์ฒญ๋ง๋ค ์ปค๋์ ๋ค์ ๋ง๋๋ ๋์ ์์ฒญ ์ฌ์ด์ ์ปค๋์ ๋ถํ ๋ ์ํ๋ก ์ ์งํ๋ ์๊ตฌ ์์ปค ๋ชจ๋๋ก Symfony๋ฅผ ์คํํฉ๋๋ค.
Symfony๋ฅผ ์ํ Docker Compose ๊ฐ๋ฐ ํ๊ฒฝ
์ข์ ๊ฐ๋ฐ ํ๊ฒฝ์ ํธ์คํธ ๋จธ์ ์ ๊ฑด๋๋ฆฌ์ง ์๊ณ ๋ ๋ชจ๋ ๊ธฐ์ฌ์์๊ฒ ๋์ผํ PHP ๋ฒ์ , ๋์ผํ ํ์ฅ, ๋์ผํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. Docker Compose๋ ๊ทธ ์คํ์ ์ ์ธ์ ์ผ๋ก ๊ธฐ์ ํฉ๋๋ค. ์๋ ์์ ๋ ๋ก์ปฌ Dockerfile๋ก ๋น๋ํ FrankenPHP ์ปจํ
์ด๋๋ฅผ PostgreSQL 17 ์๋น์ค์ ์ง์ง์ด ๊ธฐ๋ณธ Compose ๋คํธ์ํฌ๋ก ์ฐ๊ฒฐํฉ๋๋ค.
# compose.yaml
services:
php:
build:
context: .
target: frankenphp_dev
ports:
- "80:80"
- "443:443"
volumes:
- ./:/app # bind mount: edits on the host appear instantly
- caddy_data:/data # persist TLS certificates across restarts
environment:
DATABASE_URL: "postgresql://app:app@database:5432/app?serverVersion=17"
depends_on:
- database
database:
image: postgres:17-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: app
volumes:
- db_data:/var/lib/postgresql/data
volumes:
caddy_data:
db_data:php ์๋น์ค์ ๋ฐ์ธ๋ ๋ง์ดํธ๋ ํ๋ก์ ํธ ๋ฃจํธ๋ฅผ ์ปจํ
์ด๋ ์์ผ๋ก ๋งคํํ๋ฏ๋ก, ํ์ผ ๋ณ๊ฒฝ์ด ์ด๋ฏธ์ง๋ฅผ ๋ค์ ๋น๋ํ์ง ์์๋ ๋ฐ์๋ฉ๋๋ค. FrankenPHP๋ ์ฒซ ๋ถํ
์ ๋ก์ปฌ TLS ์ธ์ฆ์๋ฅผ ์์ฑํ๋๋ฐ, ์ด ๋๋ฌธ์ 443 ํฌํธ๋ฅผ ๋
ธ์ถํ๊ณ caddy_data๋ฅผ ๋ช
๋ช
๋ ๋ณผ๋ฅจ์ผ๋ก ๋๋ ๊ฒ์
๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ธ์ฆ์๊ฐ docker compose down ์ดํ์๋ ์ ์ง๋ฉ๋๋ค. DSN์ serverVersion=17 ํํธ๋ Doctrine์ด ์ฐ๊ฒฐ๋ง๋ค ๋ฒ์ ๊ฐ์ง ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๊ฒ์ ์๋ตํ๊ฒ ํด์ค๋๋ค.
์คํ์ด ์คํ๋๋ฉด ๋ชจ๋ Symfony ๋ช
๋ น์ ํธ์คํธ๊ฐ ์๋๋ผ php ์ปจํ
์ด๋ ์์์ ์คํ๋๋ฉฐ, ์ด๋ ์ฌ๋ฐ๋ฅธ PHP ๋ฒ์ ๊ณผ ํ์ฅ์ ๋ณด์ฅํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ, ๋ง์ด๊ทธ๋ ์ด์
์คํ, ์
ธ ์ด๊ธฐ ๋ชจ๋ docker compose exec๋ฅผ ๊ฑฐ์น๋ ๋์ผํ ํจํด์ ๋ฐ๋ฆ
๋๋ค.
Symfony์ Composer ํธ์ถ ์์ docker compose exec php๋ฅผ ๋ถ์ด๋ฉด ์ปจํ
์ด๋์ PHP ๋ฐํ์์ ๋์์ผ๋ก ์คํ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด docker compose exec php bin/console make:entity๋ docker compose exec php composer require symfony/uid์ฒ๋ผ ์ฌ์ฉํฉ๋๋ค. ํธ์คํธ์๋ PHP๋ฅผ ์ค์นํ ํ์๊ฐ ์ ํ ์์ต๋๋ค.
Symfony ํ๋ก๋์ ์ ์ํ ๋ฉํฐ ์คํ ์ด์ง Dockerfile
๋ฉํฐ ์คํ ์ด์ง Dockerfile์ ๊ณตํต ๋ฒ ์ด์ค๋ฅผ ๊ณต์ ํ ๋ค ๊ฐ๋ฐ ํ๊น๊ณผ ํ๋ก๋์ ํ๊น์ผ๋ก ๊ฐ๋ผ์ง๋๋ค. ํ๋ก๋์ ์คํ ์ด์ง๋ ๋ฐํ์ ์์กด์ฑ๋ง ์ค์นํ๊ณ , Composer ๊ฐ๋ฐ ํจํค์ง๋ฅผ ์ ๊ฑฐํ๋ฉฐ, ๋น๋ ์์ ์ ์บ์๋ฅผ ์๋ฐ์ ํ์ฌ ์คํ ์ค์ธ ์ปจํ ์ด๋๊ฐ ์ฆ์ ๊ธฐ๋๋๊ฒ ํฉ๋๋ค.
# Dockerfile
FROM dunglas/frankenphp:1-php8.4 AS base
WORKDIR /app
# Install the PHP extensions Symfony relies on
RUN install-php-extensions \
intl \
opcache \
pdo_pgsql \
zip
COPY /composer /usr/bin/composer
# --- Development target ---
FROM base AS frankenphp_dev
ENV APP_ENV=dev
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
# --- Production target ---
FROM base AS frankenphp_prod
ENV APP_ENV=prod
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
ENV APP_RUNTIME="Runtime\FrankenPhpSymfony\Runtime"
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# Layer caching: copy manifests first, install, then copy the source
COPY composer.json composer.lock symfony.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
COPY . .
RUN composer dump-autoload --no-dev --classmap-authoritative \
&& composer dump-env prod \
&& bin/console cache:warmup๋๋จธ์ง ์์ค๋ณด๋ค composer.json๊ณผ ๋ฝ ํ์ผ์ ๋จผ์ ๋ณต์ฌํ๋ ๊ฒ์ด ํต์ฌ ์ต์ ํ์
๋๋ค. Docker๋ ์์กด์ฑ ์ค์น ๋ ์ด์ด๋ฅผ ์บ์ํ๋ฉฐ, ๋งค ์ฝ๋ ์์ ์ด ์๋๋ผ ๋งค๋ํ์คํธ๊ฐ ๋ฐ๋ ๋๋ง ์ด ๋ ์ด์ด๋ฅผ ๋ค์ ์คํํฉ๋๋ค. --classmap-authoritative ํ๋๊ทธ๋ ํด๋์ค๋งต์ด ์์ ํ๋ค๊ณ Composer์ ์๋ ค์ฃผ๋ฏ๋ก, ์คํ ๋ก๋๊ฐ ๋ฐํ์์ ํ์ผ์์คํ
์กฐํ๋ก ๋๋์๊ฐ๋ ์ผ์ด ์์ต๋๋ค. ๋น๋ ์ค์ ์บ์๋ฅผ ์๋ฐ์
ํ๋ฉด ์ฒซ ํ๋ก๋์
์์ฒญ์ด ์์ ํ ์ปดํ์ผ๋ ์ปจํ
์ด๋์ ๋๋ฌํฉ๋๋ค.
FrankenPHP ์์ปค ๋ชจ๋์ Symfony ๋ฐํ์
์ ํต์ ์ธ PHP-FPM์ Symfony ์ปค๋์ ๋ถํ
ํ๊ณ , ์์ฒญ ํ๋๋ฅผ ์ฒ๋ฆฌํ ๋ค, ๋ชจ๋ ๊ฒ์ ํ๊ธฐํฉ๋๋ค. ์์ปค ๋ชจ๋๋ ์ปค๋๊ณผ ์์กด์ฑ ์ฃผ์
์ปจํ
์ด๋๋ฅผ ์์ฒญ ์ฌ์ด์ ์ด์ ์๊ฒ ์ ์งํ๋ฉฐ, ์ฌ๊ธฐ์ ๋๋ถ๋ถ์ ์ง์ฐ ์๊ฐ ์ ๊ฐ์ด ๋์ต๋๋ค. runtime/frankenphp-symfony ํจํค์ง๋ Symfony Runtime ์ปดํฌ๋ํธ์ ํ์ค public/index.php ๋ถํธ์คํธ๋ฉ์ ํตํด ์ด๋ฅผ ๋์์ํต๋๋ค.
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};Dockerfile์ ์ค์ ๋ FRANKENPHP_CONFIG="worker ./public/index.php" ๋ผ์ธ์ด ์ด ๋ฃจํ๋ฅผ ํ์ฑํํฉ๋๋ค. ์ปจํ
์ด๋๊ฐ ์ฌ์ฌ์ฉ๋๋ฏ๋ก, ์์ฒญ๋ณ ์ํ๋ฅผ ๋ณด์ ํ๋ ์๋น์ค๋ ์์ฒญ ์ฌ์ด์ ์ค์ค๋ก๋ฅผ ์ด๊ธฐํํด์ผ ํฉ๋๋ค. Symfony๋ ํ๋ ์์ํฌ ์๋น์ค๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ์ง๋ง, ์ปค์คํ
์ํ ์ ์ง ์๋น์ค๋ Symfony\Contracts\Service\ResetInterface๋ฅผ ๊ตฌํํ๊ณ kernel.reset ํ๊ทธ๋ฅผ ๋ถ์ฌ์ผ ๋์ ๋ ์ํ๊ฐ ๋ค์ ์์ฒญ์ผ๋ก ์์ด ๋๊ฐ์ง ์์ต๋๋ค. ์ด๋ ์ฅ์๊ฐ ์คํ๋๋ Messenger ์์ปค๊ฐ ์๊ตฌํ๋ ๊ฒ๊ณผ ๋์ผํ ๊ท์จ์ด๋ฉฐ, ๊ทธ ๋ณด์์ ๋์ผํ ํ๋์จ์ด์์ PHP-FPM๋ณด๋ค ๋ช ๋ฐฐ ๋์ ์์ฒญ ์ฒ๋ฆฌ๋์
๋๋ค.
๋ก์ปฌ ๊ฐ๋ฐ์์๋ --watch ํ๋๊ทธ๋ก FrankenPHP๋ฅผ ์คํํ๋ฉด ํ์ผ์ด ๋ณ๊ฒฝ๋ ๋ ์์ปค๊ฐ ์๋์ผ๋ก ์ฌ์์๋๋ฏ๋ก, ์์ปค ๋ชจ๋๊ฐ ํธ์ง-์๋ก๊ณ ์นจ ๋ฃจํ๋ฅผ ๋ฐฉํดํ์ง ์์ต๋๋ค.
Symfony์์ FrankenPHP์ PHP-FPM ๋น๊ต
FrankenPHP์ ๊ณ ์ ์ ์ธ Nginx + PHP-FPM ์คํ ์ค ๋ฌด์์ ์ ํํ๋๋๋ Dockerfile๊ณผ ๋ฐํ์ ๋์์ ๋ชจ๋ ์ข์ฐํฉ๋๋ค. ์๋ ํ๋ Symfony ํ๋ก๋์ ํ๊ฒฝ์์์ ์ค์ง์ ์ธ ์ฐจ์ด๋ฅผ ์ ๋ฆฌํฉ๋๋ค.
| ํญ๋ชฉ | FrankenPHP ์์ปค ๋ชจ๋ | Nginx + PHP-FPM | |--------|------------------------|-----------------| | ์ปจํ ์ด๋ | ํ๋ | ๋ (์น ์๋ฒ + PHP) | | ์ปค๋ ๋ถํ | ์์ปค๋น ํ ๋ฒ | ์์ฒญ๋น ํ ๋ฒ | | HTTP/3 | ๋ด์ฅ | ์ถ๊ฐ ์ค์ ํ์ | | TLS | ์๋ (Caddy) | ์๋ ์ธ์ฆ์ ์ค์ | | ์ํ ์ฒ๋ฆฌ | ์์ฒญ ์ฌ์ด์ ์ด๊ธฐํ ํ์ | ์์ฒญ๋ณ ๊ฒฉ๋ฆฌ | | ์ฝ๋ ์คํํธ ๋น์ฉ | ๋ถํ ์ ํ ๋ฒ ์ง๋ถ | ๋งค ์์ฒญ๋ง๋ค ์ง๋ถ |
์์ปค ๋ชจ๋๋ ๊ฐ๋น์ผ ์ปค๋ ๋ฐ ์ปจํ ์ด๋ ์ปดํ์ผ ๋จ๊ณ๋ฅผ ์์ฒ ๊ฑด์ ์์ฒญ์ ๊ฑธ์ณ ๋ถ์ฐํ๊ธฐ ๋๋ฌธ์ ์ฒ๋ฆฌ๋์์ ์ฐ์๋ฅผ ์ ํฉ๋๋ค. PHP-FPM์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ ์ญ ๋ณ์์ ์์กดํ๊ฑฐ๋ ์์ฒญ๋ง๋ค ์ ํ๋ก์ธ์ค๋ฅผ ์ ์ ๋ก ํ๋ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๋ ์ฌ์ ํ ์ ํจํฉ๋๋ค. ๊ทธ๋ฐ ์ฝ๋๋ ์ฌ์ฌ์ฉ๋๋ ์์ปค์์ ๊นจ์ง๊ธฐ ๋๋ฌธ์ ๋๋ค.
์์ฒญ ๋ฐ์ดํฐ๋ฅผ ํ๋ผ์ด๋น ์์ฑ์ ์บ์ํ๋ ์๋น์ค๋ ์ด๊ธฐํ๋์ง ์์ผ๋ฉด ํ ์์ฒญ์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ์์ฒญ์ ๊ทธ๋๋ก ์ ๊ณตํฉ๋๋ค. ํ๋ก๋์ ์์ ์์ปค ๋ชจ๋๋ก ์ ํํ๊ธฐ ์ ์ ์ฑ๊ธํด, ์ด๋ฒคํธ ๊ตฌ๋ ์, ๊ทธ๋ฆฌ๊ณ ๋ณด์ ํ ํฐ์ด๋ ํ์ฌ ์์ฒญ์ ๋ณด์ ํ๋ ๋ชจ๋ ๊ฒ์ ์ ๊ฒํ์ญ์์ค.
Symfony ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
ํ๋ก๋์ ์์ ํ๊ฒฝ ๋ณ์์ ์ํฌ๋ฆฟ ๊ด๋ฆฌํ๊ธฐ
Symfony๋ ํ๊ฒฝ ๋ณ์์์ ์ค์ ์ ์ฝ์ด๋ค์ด๋ฉฐ, ํ๋ก๋์ ์์ ์ด๋ฅผ ๊ณต๊ธํ๋ ๊ฒฌ์คํ ๋ฐฉ๋ฒ์ด ๋ ๊ฐ์ง ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ๋ ์ค์ผ์คํธ๋ ์ดํฐ๋ Compose ํ์ผ์ ํตํด ์ผ๋ฐ ๋ณ์๋ฅผ ์ฃผ์ ํ๋ ๊ฒ์ ๋๋ค. ๋ ๋ฒ์งธ๋ Symfony์ ์ํธํ๋ ์ํฌ๋ฆฟ ๋ณผํธ๋ก, ๋ฏผ๊ฐํ ๊ฐ์ ์ํธ๋ฌธ์ผ๋ก ์ ์ฅ์์ ์ ์ฅํ๋ค๊ฐ ๋ฐํ์์ ๋จ์ผ ๊ฐ์ธ ํค๋ก ๋ณตํธํํฉ๋๋ค.
# Generate the keypair; commit the public key, keep the private key out of the image
php bin/console secrets:generate-keys
# Encrypt a value into config/secrets/prod/
php bin/console secrets:set DATABASE_URL
# In production, expose only the decryption key to the container
export SYMFONY_DECRYPTION_SECRET="$(cat config/secrets/prod/prod.decrypt.private.php)"Dockerfile์ composer dump-env prod ๋จ๊ณ๋ .env ํ์ผ๋ค์ ์ต์ ํ๋ ๋จ์ผ .env.local.php๋ก ์ปดํ์ผํ์ฌ, ๋ถํ
์ dotenv ํ์ผ์ ํ์ฑํ๋ ๋ฐํ์ ๋น์ฉ์ ์ ๊ฑฐํฉ๋๋ค. ์ค์ ์ปจํ
์ด๋ ํ๊ฒฝ์ ์ค์ ๋ ๋ณ์๋ ์ฌ์ ํ ์ปดํ์ผ๋ ๊ธฐ๋ณธ๊ฐ์ ์ฌ์ ์ํ๋ฏ๋ก, ์ค์ผ์คํธ๋ ์ดํฐ๊ฐ ์ ๊ณตํ ์ํฌ๋ฆฟ์ด ํญ์ ์ฐ์ ํฉ๋๋ค. Symfony ๋ฐฐํฌ ๊ฐ์ด๋๋ ์ ์ฒด ์ฐ์ ์์ ์์๋ฅผ ๋ฌธ์ํํ์ง๋ง, ์ค์ฉ์ ์ธ ๊ท์น์ ๋จ์ํฉ๋๋ค. APP_SECRET์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๊ฒฉ ์ฆ๋ช
์ ์ ๋ ์ด๋ฏธ์ง์ ๊ตฝ์ง ๋ง๊ณ , ์ปจํ
์ด๋ ์์ ์์ ์ ์ฃผ์
ํ์ญ์์ค.
Symfony ํ๋ก๋์ ์ด๋ฏธ์ง ์ต์ ํ
๋ ๊ฐ์ง OPcache ์ค์ ์ด ํ๋ก๋์ ์ฑ๋ฅ ๊ฒฉ์ฐจ์ ๋๋ถ๋ถ์ ์ฐจ์งํฉ๋๋ค. ํ์์คํฌํ ๊ฒ์ฆ ๋นํ์ฑํ์ ํ๋ฆฌ๋ก๋ฉ ํ์ฑํ์ ๋๋ค. ์ปจํ ์ด๋ ์ด๋ฏธ์ง๋ ๋ถ๋ณ์ด๋ฏ๋ก ์์ค๊ฐ ๋ฐํ์์ ๋ฐ๋์ง ์์ผ๋ฉฐ, ๋ฐ๋ผ์ OPcache๊ฐ ํธ์ง ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ค๊ณ ํ์ผ์ stat ํ ์ด์ ๊ฐ ์ ํ ์์ต๋๋ค. ํ๋ฆฌ๋ก๋ฉ์ ํ ๊ฑธ์ ๋ ๋์๊ฐ ์๋ฒ ์์ ์ Symfony์ ์ ํ๋ฆฌ์ผ์ด์ ํด๋์ค๋ฅผ ๊ณต์ ๋ฉ๋ชจ๋ฆฌ์ ํ ๋ฒ ๋ก๋ํฉ๋๋ค.
; docker/php/prod.ini
opcache.enable=1
opcache.preload=/app/var/cache/prod/App_KernelProdContainer.preload.php
opcache.preload_user=root
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; immutable image: never re-check the filesystem
realpath_cache_size=4096K
realpath_cache_ttl=600Symfony๋ ์์ ๋์ด๋ preload.php ํ์ผ์ cache:warmup ์ค์ ์์ฑํ๋ฏ๋ก, ๋น๋ ์ดํ ์ด๋ฏธ์ง ์์ ์ด ํ์ผ์ด ์ด๋ฏธ ์กด์ฌํฉ๋๋ค. OPcache ํ๋ฆฌ๋ก๋ฉ์ ์์ ์ ์ด ํด๋์ค๋ค์ ๋งํฌํ๊ณ , ํด๋น ํด๋์ค๊ฐ ํ์ํ ์ฒซ ์์ฒญ์์ ์ปดํ์ผ ๋จ๊ณ๋ฅผ ๊ฑด๋๋๋๋ค. validate_timestamps=0 ์ค์ ์ ์ ๋ฆด๋ฆฌ์ค๊ฐ ๊ณง ์ ์ด๋ฏธ์ง๋ฅผ ์๋ฏธํ๋ ๋ถ๋ณ ๋ฐฐํฌ์์๋ง ์์ ํฉ๋๋ค. ๊ฐ๋ณ ํธ์คํธ์์๋ ์ค๋๋ ์ฝ๋๋ฅผ ์ ๊ณตํ๊ฒ ๋ฉ๋๋ค. max_accelerated_files๋ฅผ ์ค์ ํ์ผ ์๋ณด๋ค ํฌ๊ฒ ์ก์ผ๋ฉด ํ๋ ์์ํฌ ์ ์ฒด๊ฐ ์ถ์ถ ์์ด ์บ์๋ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.
.dockerignore๋ก Symfony ์ด๋ฏธ์ง ํฌ๊ธฐ ์ค์ด๊ธฐ
.dockerignore ํ์ผ์ด ์์ผ๋ฉด ๋น๋๊ฐ ์กฐ์ฉํ ๋น๋ํด์ง๋๋ค. ์ด ํ์ผ์ด ์์ผ๋ฉด COPY . . ๋จ๊ณ๊ฐ ๋ก์ปฌ vendor ๋๋ ํฐ๋ฆฌ, ๊ฐ๋ฐ์ฉ var/cache, ๋
ธ๋ ๋น๋ ์ฐ์ถ๋ฌผ, ๊ทธ๋ฆฌ๊ณ .git ํ์คํ ๋ฆฌ๋ฅผ ๊ทธ๋๋ก ์ด๋ฏธ์ง์ ๋น๋ ์ปจํ
์คํธ๋ก ์ค์ด ๋๋ฆ
๋๋ค. ์ด๊ฒ๋ค์ ์ ์ธํ๋ฉด ์ด๋ฏธ์ง๊ฐ ์์์ง๊ณ , ๋น๋ ์ปจํ
์คํธ๋ฅผ ๋ฐ๋ชฌ์ผ๋ก ์
๋ก๋ํ๋ ์๋๊ฐ ๋นจ๋ผ์ง๋ฉฐ, ๊ฐ๋ฐ ์ฐ์ถ๋ฌผ์ด ๋ฐฉ๊ธ ์ค์นํ ํ๋ก๋์
์์กด์ฑ์ ๋ฎ์ด์ฐ๋ ์ผ์ ๋ง์์ค๋๋ค.
# .dockerignore
/.git/
/vendor/
/node_modules/
/var/
/.env.local
/.env.*.local
/tests/
/docker/
compose*.yaml
Dockerfile/vendor/๋ฅผ ๋ฌด์ํ๋ ๊ฒ์ด ๊ฐ์ฅ ์ค์ํฉ๋๋ค. ํ๋ก๋์
์คํ
์ด์ง๋ composer install --no-dev๋ฅผ ์คํํ๋ฏ๋ก, ํธ์คํธ์ ๊ฐ๋ฐ์ฉ vendor ๋๋ ํฐ๋ฆฌ๋ฅผ ์ค์ด ๋๋ฅด๋ฉด ๊ทธ ๋จ๊ณ๊ฐ ์์ ํ ๋ฌด์๋ฏธํด์ง๋๋ค. /var/๋ฅผ ์ ์ธํ๋ฉด ๊ฐ๋ฐ ์บ์์ ๋ก๊ทธ๊ฐ ์ด๋ฏธ์ง์์ ๋น ์ ธ, ๋น๋ ์์ ์ cache:warmup์ด ๊นจ๋ํ ํ๋ก๋์
์บ์๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ๋ฉํฐ ์คํ
์ด์ง ๋น๋์ Alpine ๊ธฐ๋ฐ ํ์ฅ์ ๊ฒฐํฉํ๋ฉด, ๊ตฐ๋๋๊ธฐ ์๋ Symfony ์ด๋ฏธ์ง๋ ๋ณดํต 150 MB๋ฅผ ๋๋ํ ๋ฐ๋๋๋ค.
Symfony ์ปจํ ์ด๋ ๋ฐฐํฌ์ ๋ง์ด๊ทธ๋ ์ด์ ์คํ
ํ๋ก๋์ Compose ํ์ผ์ ๋ก์ปฌ์์ ๋น๋ํ๋ ๋์ ๋ ์ง์คํธ๋ฆฌ์ ์ฌ์ ๋น๋๋ ์ด๋ฏธ์ง๋ฅผ ์ฐธ์กฐํ๋ฉฐ, ์๋ตํ๋ ์ปจํ ์ด๋๋ก๋ง ์ค์ผ์คํธ๋ ์ดํฐ๊ฐ ํธ๋ํฝ์ ๋ผ์ฐํ ํ๋๋ก ํฌ์ค ์ฒดํฌ๋ฅผ ์ ์ํฉ๋๋ค. ์ํฌ๋ฆฟ์ ์ด๋ฏธ์ง ๋ ์ด์ด๊ฐ ์๋๋ผ ํ๊ฒฝ ๋ณ์๋ก ๋์ฐฉํฉ๋๋ค.
# compose.prod.yaml
services:
php:
image: registry.example.com/app:${TAG}
environment:
APP_ENV: prod
APP_SECRET: ${APP_SECRET}
DATABASE_URL: ${DATABASE_URL}
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost/health"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stoppedํฌ์ค ์ฒดํฌ๋ /health ๋ผ์ฐํธ๋ฅผ curl ํ๋ฏ๋ก, ์ ํ๋ฆฌ์ผ์ด์
์ ์ด ๋ผ์ฐํธ๋ฅผ ๋
ธ์ถํด์ผ ํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ฑด๋๋ฆฌ์ง ์๊ณ 200์ ๋ฐํํ๋ ์ต์ํ์ ์ปจํธ๋กค๋ฌ๋ ์ฒดํฌ๋ฅผ ๋น ๋ฅด๊ฒ ์ ์งํ๊ณ , ์ผ์์ ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฅ์ ๊ฐ ์ผ์ด๋๋ ์ปจํ
์ด๋๋ฅผ unhealthy๋ก ํ์ํ๋ ๊ฒ์ ํผํ๊ฒ ํด์ค๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๋น ์ํ๊ฐ ์ค์ํ๋ค๋ฉด ๋ ๋ฒ์งธ ์ฌ์ธต ํ๋ก๋ธ๊ฐ ๊ฐ๋ฒผ์ด ์ฟผ๋ฆฌ๋ฅผ ์คํํ ์ ์์ง๋ง, ๋ผ์ด๋ธ๋์ค ์๋ํฌ์ธํธ ์์ฒด๋ ์๋ช
ํ๊ฒ ๋จ์ํ ์ํ๋ก ์ ์งํฉ๋๋ค.
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
final class HealthController extends AbstractController
{
#[Route('/health', name: 'health', methods: ['GET'])]
public function __invoke(): JsonResponse
{
return new JsonResponse(['status' => 'ok']);
}
}๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ์ ์ ํ๋ฆฌ์ผ์ด์ ๋ถํ ์์ด ์๋๋ผ, ์ ์ปจํ ์ด๋๊ฐ ํธ๋ํฝ์ ๋ฐ๊ธฐ ์ ์ ๋ณ๊ฐ์ ๋จ๊ณ๋ก ์คํ๋์ด์ผ ํฉ๋๋ค. ์ผํ์ฑ ์ปจํ ์ด๋์์ ๋ง์ด๊ทธ๋ ์ด์ ์ ์คํํ๋ฉด, ์ฌ๋ฌ ์ ํ๋ฆฌ์ผ์ด์ ๋ ํ๋ฆฌ์นด๊ฐ ๋ณ๋ ฌ๋ก ์์๋๋๋ผ๋ ๋ฆด๋ฆฌ์ค๋ง๋ค ์ ํํ ํ ๋ฒ์ฉ ์คํค๋ง๊ฐ ์ต์ ์ํ๊ฐ ๋ฉ๋๋ค.
# deploy.sh
set -euo pipefail
docker compose -f compose.prod.yaml pull
# Apply migrations once, in an isolated container
docker compose -f compose.prod.yaml run --rm php \
bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
# Start replicas and wait for the health check to pass
docker compose -f compose.prod.yaml up -d --wait--wait ํ๋๊ทธ๋ ๋ชจ๋ ์๋น์ค๊ฐ healthy๋ฅผ ๋ณด๊ณ ํ ๋๊น์ง ๋ธ๋กํนํ์ฌ, ๋ฐฐํฌ ์คํฌ๋ฆฝํธ์ ๋ฐ์ฌ ํ ๋ฐฉ์น์ ์์์ด ์๋๋ผ ์ง์ง ์ฑ๊ณต ์ ํธ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ Kubernetes๋ Docker Swarm ๊ฐ์ ์ค์ผ์คํธ๋ ์ดํฐ์ ๋กค๋ง ์
๋ฐ์ดํธ์ ๊ฒฐํฉํ๋ฉด ๋ฌด์ค๋จ ๋ฆด๋ฆฌ์ค๊ฐ ๋ฉ๋๋ค. ์ ์ปจํ
์ด๋๊ฐ ํฌ์ค ์ฒดํฌ๋ฅผ ํต๊ณผํ ๋๊น์ง ๊ธฐ์กด ์ปจํ
์ด๋๊ฐ ๊ณ์ ์๋นํฉ๋๋ค. ์ด ์ํฌํ๋ก๊ฐ ๋์์ผ๋ก ์ผ๋ ํ๋ ์์ํฌ ๋ฒ์ ์ ๋ ํญ๋๊ฒ ๋ณด๋ ค๋ฉด, Symfony ํ๋ซํผ ๊ฐ์์ Symfony 8 ๋ฐ PHP 8.4 ๊ธฐ๋ฅ ๊ฐ์ด๋๊ฐ ํ์ฌ ๋ฆด๋ฆฌ์ค ๋ผ์ธ์์ ๋ฌด์์ด ๋ฐ๋์๋์ง ๋ค๋ฃน๋๋ค.
๊ฒฐ๋ก
- ๋ฉํฐ ์คํ ์ด์ง Dockerfile์ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐ ์ด๋ฏธ์ง์ ํ๋ก๋์ ์ด๋ฏธ์ง๊ฐ ํ๋์ ๋ฒ ์ด์ค๋ฅผ ๊ณต์ ํ๊ฒ ํ๋, ํ๋ก๋์ ํ๊น์ Composer ๊ฐ๋ฐ ์์กด์ฑ ์์ด ๋ฐฐํฌํ์ญ์์ค
composer.json๊ณผ ๋ฝ ํ์ผ์ ์์ค๋ณด๋ค ๋จผ์ ๋ณต์ฌํ์ฌ ์์กด์ฑ ์ค์น ๋ ์ด์ด๋ฅผ ์ฝ๋ ๋ณ๊ฒฝ์ ๊ฑธ์ณ ์บ์๋ ์ํ๋ก ์ ์งํ์ญ์์ค- FrankenPHP๋ฅผ ์์ปค ๋ชจ๋๋ก ์คํํ์ฌ ๋ถํ
๋ ์ปค๋์ ์์ฒญ ์ฌ์ด์ ์ฌ์ฌ์ฉํ๊ณ , ์ํ ์ ์ง ์๋น์ค๋
ResetInterface๋ก ์ด๊ธฐํํ์ฌ ์ํ ๋์๋ฅผ ๋ฐฉ์งํ์ญ์์ค - ๋น๋ ์์ ์ Symfony ์บ์๋ฅผ ์๋ฐ์
ํ๊ณ
dump-env prod๋ก.env๋ฅผ ์ปดํ์ผํ์ฌ, ์คํ ์ค์ธ ์ปจํ ์ด๋๊ฐ ์์ ํ ์ด๊ธฐํ๋ ์ํ๋ก ๊ธฐ๋๋๊ฒ ํ์ญ์์ค - ํ๋ก๋์
์์ OPcache ํ๋ฆฌ๋ก๋ฉ์ ํ์ฑํํ๊ณ
validate_timestamps=0์ ์ค์ ํ์ญ์์ค. ์ด๋ ์ด๋ฏธ์ง๊ฐ ๋ถ๋ณ์ด๊ธฐ ๋๋ฌธ์ ์ ํํ ์์ ํฉ๋๋ค APP_SECRET, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๊ฒฉ ์ฆ๋ช , ๋ณผํธ ๋ณตํธํ ํค๋ฅผ ์ด๋ฏธ์ง ๋ ์ด์ด ๋ฐ์ ๋๊ณ , ์ปจํ ์ด๋ ์์ ์์ ์ ํ๊ฒฝ ๋ณ์๋ก ์ฃผ์ ํ์ญ์์ค- Doctrine ๋ง์ด๊ทธ๋ ์ด์
์ ์ ๋ ํ๋ฆฌ์นด๊ฐ ํธ๋ํฝ์ ๋ฐ๊ธฐ ์ ์ ์ผํ์ฑ ์ปจํ
์ด๋์์ ์ ์ฉํ๊ณ ,
up -d --wait๋ก ํฌ์ค ์ฒดํฌ์ ๋กค์์์ ๊ฑธ์ด๋์ญ์์ค
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
ํ๊ทธ
๊ณต์
๊ด๋ จ ๊ธฐ์ฌ

Symfony 8 ์๋ฒฝ ๊ฐ์ด๋: PHP 8.4 ๋ ์ด์ง ์ค๋ธ์ ํธ, ๋ฉํฐ์คํ ํผ, 2026๋ ๋ฉด์ ๋๋น๊น์ง
Symfony 8์ PHP 8.4๋ฅผ ํ์๋ก ์๊ตฌํ๋ฉฐ ๋ค์ดํฐ๋ธ ๋ ์ด์ง ์ค๋ธ์ ํธ, AbstractFlowType, ํธ์ถ ๊ฐ๋ฅ ์ปค๋งจ๋ ๋ฑ ๋ค์์ ์ ๊ธฐ๋ฅ์ ํ์ฌํ์ต๋๋ค. ์ฃผ์ ๊ธฐ๋ฅ์ ์ฝ๋ ์์ ์ ํจ๊ป ๋ถ์ํ๊ณ 2026๋ ๋ฉด์ ๋๋น ํฌ์ธํธ๋ฅผ ์ ๋ฆฌํฉ๋๋ค.

Doctrine ORM: Symfony์์ ๊ด๊ณ ๋ง์คํฐํ๊ธฐ
Symfony์ Doctrine ORM ๊ด๊ณ์ ๋ํ ์๋ฒฝ ๊ฐ์ด๋. OneToMany, ManyToMany, ๋ก๋ฉ ์ ๋ต, ๊ทธ๋ฆฌ๊ณ ์ค์ฉ์ ์ธ ์์ ๋ฅผ ํตํ ์ฑ๋ฅ ์ต์ ํ.

Symfony ์ธํฐ๋ทฐ ์ง๋ฌธ: 2026๋ Top 25
๊ฐ์ฅ ๋ง์ด ๋ฌป๋ Symfony ์ธํฐ๋ทฐ ์ง๋ฌธ 25์ . ์ํคํ ์ฒ, Doctrine ORM, ์๋น์ค, ๋ณด์, ํผ, ํ ์คํธ๋ฅผ ์์ธํ ๋ต๋ณ๊ณผ ์ฝ๋ ์์ ๋ก ๋ค๋ฃน๋๋ค.