# Symfony Dockerfile Generator

URL: /dockerizer/symfony

Generate a production Dockerfile for Symfony on FrankenPHP — production cache warmed at build time, OPcache tuned, running as www-data.

## Default configuration

- `appName` (App name): symfony — Used for the image tag, the compose service and the OCI labels.

- `port` (Port): 8080 — 8080 rather than 80 so the web server runs entirely as an unprivileged user. Publish it as 80 with `-p 80:8080`.

- `documentRoot` (Document root): public — The directory holding index.php, relative to the project root. Only this directory is web-reachable.

- `server` (Web server): frankenphp — FrankenPHP embeds PHP in a Caddy server: one process, HTTP/2 and HTTP/3, and worker mode if you want it. Apache with mod_php is the classic single-process alternative.

- `phpVersion` (PHP version): 8.4

- `phpExtensions` (PHP extensions): pdo_pgsql pdo_mysql intl zip opcache apcu — Space-separated. Installed with install-php-extensions, which pulls the build dependencies and removes them again afterwards.

- `phpMemoryLimit` (memory_limit): 256M

- `phpUploadMaxFilesize` (upload_max_filesize): 32M

- `phpPostMaxSize` (post_max_size): 32M

- `phpMaxExecutionTime` (max_execution_time): 60

- `phpTimezone` (date.timezone): UTC

- `database` (Database service): none — Adds the database to docker-compose.yml with a healthcheck, a named volume and a DATABASE_URL wired into the app.

- `redis` (Redis service): false — Adds Redis to docker-compose.yml and exposes REDIS_URL to the app.

- `cacheMounts` (BuildKit cache mounts): true — Persists the package manager store between builds. Repeat builds skip the download entirely.

- `multiArch` (Multi-architecture build): false — Adds BUILDPLATFORM/TARGETARCH so `docker buildx build --platform linux/amd64,linux/arm64` cross-compiles natively.

- `healthcheck` (Healthcheck): true — Adds a HEALTHCHECK so orchestrators can restart an unresponsive container.

- `ociLabels` (OCI labels): false — Adds org.opencontainers.image.* metadata to the final image.

- `buildSecret` (Build secret mount): false — Reads a private registry token via --mount=type=secret so it never lands in an image layer.

## Generated files

### Dockerfile

Multi-stage build: dependencies, compilation and the runtime image are separate, so only what the app needs at runtime ships.

```docker
# syntax=docker/dockerfile:1
# Generated by Easypanel Dockerizer — https://easypanel.io/dockerizer

# --- Base ---------------------------------------------
FROM dunglas/frankenphp:1-php8.4-alpine AS base
WORKDIR /app

# install-php-extensions resolves the build dependencies for each extension and removes them afterwards
COPY --from=mlocati/php-extension-installer:latest /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions pdo_pgsql pdo_mysql intl zip opcache apcu

# Production php.ini, copied over the image defaults
COPY php.ini /usr/local/etc/php/conf.d/zz-app.ini

# --- Dependencies -------------------------------------
FROM base AS vendor
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

# Manifests only — application edits must not re-resolve the tree
COPY composer.json composer.lock* ./

# --no-scripts and --no-autoloader because the application is not in place yet
RUN --mount=type=cache,id=composer-cache,target=/root/.composer/cache,sharing=locked \
    composer install --no-dev --no-interaction --prefer-dist --no-progress --optimize-autoloader --no-scripts --no-autoloader

# --- Runtime ------------------------------------------
FROM base AS runtime
COPY Caddyfile /etc/frankenphp/Caddyfile

# Caddy keeps its certificate and config state here; www-data has to own it
RUN chown -R www-data:www-data /data /config

# Vendor tree first, so application edits do not invalidate it
COPY --from=vendor --chown=www-data:www-data /app/vendor ./vendor
COPY --chown=www-data:www-data . .
RUN --mount=type=bind,from=composer:2,source=/usr/bin/composer,target=/usr/bin/composer \
    composer dump-autoload --optimize --classmap-authoritative --no-dev

# Warm the production cache so the first request is not the slow one
ENV APP_ENV=prod
ENV APP_DEBUG=0
RUN php bin/console cache:warmup --env=prod
USER www-data
EXPOSE 8080
STOPSIGNAL SIGTERM

# Lets Docker, Compose and Easypanel see when the app is wedged
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
  CMD ["php", "-r", "exit(@file_get_contents('http://127.0.0.1:8080/') === false ? 1 : 0);"]

# No CMD: the FrankenPHP image starts Caddy itself, and the Caddyfile above points it at /app/public.

```

### .dockerignore

Keeps the build context small and stops secrets and local dependencies from reaching an image layer.

```bash
# Version control
.git
.gitignore
.github

# Secrets — never bake these into an image layer
.env
.env.*
!.env.example
*.pem
*.key

# Editor and OS noise
.vscode
.idea
.DS_Store
Thumbs.db

# Docs and local tooling
README.md
LICENSE
docs
.editorconfig
docker-compose*.yml
Dockerfile*
.dockerignore

# Composer installs inside the image
vendor

# Framework caches and user uploads
storage/logs/*
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/views/*
bootstrap/cache/*
var/cache
var/log

```

### php.ini

Production PHP settings with OPcache and JIT enabled, timestamp validation off and errors sent to stderr.

```ini
; Production php.ini overrides. Copied over the image defaults.
date.timezone = UTC
memory_limit = 256M
max_execution_time = 60
post_max_size = 32M
upload_max_filesize = 32M

expose_php = Off
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /dev/stderr

; OPcache — the single biggest PHP performance win in production.
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 192
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0
opcache.jit = tracing
opcache.jit_buffer_size = 64M

realpath_cache_size = 4096K
realpath_cache_ttl = 600

```

### Caddyfile

FrankenPHP's Caddy config — serves the front controller directly, with automatic compression and HTTP/2.

```nginx
{
	frankenphp
	order php_server before file_server
	auto_https off
}

:8080 {
	root * /app/public
	encode zstd br gzip

	# Refuse dotfiles outright — .env must never be reachable.
	@dotfiles path_regexp \/\.
	respond @dotfiles 404

	php_server {
		index index.php
	}

	header {
		X-Content-Type-Options nosniff
		X-Frame-Options SAMEORIGIN
		-Server
	}
}

```

### docker-compose.yml

Runs the image locally with its backing services, wired together and health-gated.

```yaml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "8080:8080"

```

## Frequently asked questions

### Why warm the cache during the build?

Symfony compiles the container and the routing table on first request otherwise, which makes the first user pay for it. Doing it at build time also means the container never needs to write to var/cache.

### Do I need APP_SECRET at build time?

No, only at runtime. Pass it as an environment variable — baking it into a layer means anyone who can pull the image has it.

## Related generators

- [Laravel](/dockerizer/laravel)

- [PHP](/dockerizer/php)

- [Node.js](/dockerizer/nodejs)

- [Django](/dockerizer/django)