├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── Dockerfile ├── README.md ├── compose.override.yaml ├── compose.prod.yaml ├── compose.yaml ├── docs ├── alpine.md ├── digitalocean-dns.png ├── digitalocean-droplet.png ├── existing-project.md ├── extra-services.md ├── makefile.md ├── mysql.md ├── options.md ├── production.md ├── tls.md ├── troubleshooting.md ├── updating.md └── xdebug.md └── frankenphp ├── Caddyfile ├── conf.d ├── 10-app.ini ├── 20-app.dev.ini └── 20-app.prod.ini └── docker-entrypoint.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/*.md 3 | **/*.php~ 4 | **/*.dist.php 5 | **/*.dist 6 | **/*.cache 7 | **/._* 8 | **/.dockerignore 9 | **/.DS_Store 10 | **/.git/ 11 | **/.gitattributes 12 | **/.gitignore 13 | **/.gitmodules 14 | **/compose.*.yaml 15 | **/compose.*.yml 16 | **/compose.yaml 17 | **/compose.yml 18 | **/docker-compose.*.yaml 19 | **/docker-compose.*.yml 20 | **/docker-compose.yaml 21 | **/docker-compose.yml 22 | **/Dockerfile 23 | **/Thumbs.db 24 | .github/ 25 | docs/ 26 | public/bundles/ 27 | tests/ 28 | var/ 29 | vendor/ 30 | .editorconfig 31 | .env.*.local 32 | .env.local 33 | .env.local.php 34 | .env.test 35 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 4 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | [*.{js,html,ts,tsx}] 19 | indent_size = 2 20 | 21 | [*.json] 22 | indent_size = 2 23 | 24 | [*.md] 25 | trim_trailing_whitespace = false 26 | 27 | [*.sh] 28 | indent_style = tab 29 | 30 | [*.xml.dist] 31 | indent_style = space 32 | indent_size = 4 33 | 34 | [*.{yaml,yml}] 35 | trim_trailing_whitespace = false 36 | 37 | [.github/workflows/*.yml] 38 | indent_size = 2 39 | 40 | [.gitmodules] 41 | indent_style = tab 42 | 43 | [.php_cs.dist] 44 | indent_style = space 45 | indent_size = 4 46 | 47 | [composer.json] 48 | indent_size = 4 49 | 50 | [{compose,docker-compose}.*.{yaml,yml}] 51 | indent_size = 2 52 | 53 | [*.*Dockerfile] 54 | indent_style = tab 55 | 56 | [*.*Caddyfile] 57 | indent_style = tab 58 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.conf text eol=lf 4 | *.html text eol=lf 5 | *.ini text eol=lf 6 | *.js text eol=lf 7 | *.json text eol=lf 8 | *.md text eol=lf 9 | *.php text eol=lf 10 | *.sh text eol=lf 11 | *.yaml text eol=lf 12 | *.yml text eol=lf 13 | bin/console text eol=lf 14 | composer.lock text eol=lf merge=ours 15 | 16 | *.ico binary 17 | *.png binary 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: ~ 8 | workflow_dispatch: ~ 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | tests: 16 | name: Tests 17 | runs-on: ubuntu-latest 18 | steps: 19 | - 20 | name: Checkout 21 | uses: actions/checkout@v4 22 | - 23 | name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v3 25 | - 26 | name: Build Docker images 27 | uses: docker/bake-action@v6 28 | with: 29 | pull: true 30 | load: true 31 | files: | 32 | compose.yaml 33 | compose.override.yaml 34 | set: | 35 | *.cache-from=type=gha,scope=${{github.ref}} 36 | *.cache-from=type=gha,scope=refs/heads/main 37 | *.cache-to=type=gha,scope=${{github.ref}},mode=max 38 | - 39 | name: Start services 40 | run: docker compose up --wait --no-build 41 | - 42 | name: Check HTTP reachability 43 | run: curl -v --fail-with-body http://localhost 44 | - 45 | name: Check HTTPS reachability 46 | if: false # Remove this line when the homepage will be configured, or change the path to check 47 | run: curl -vk --fail-with-body https://localhost 48 | - 49 | name: Check Mercure reachability 50 | run: curl -vkI --fail-with-body https://localhost/.well-known/mercure?topic=test 51 | - 52 | name: Create test database 53 | if: false # Remove this line if Doctrine ORM is installed 54 | run: docker compose exec -T php bin/console -e test doctrine:database:create 55 | - 56 | name: Run migrations 57 | if: false # Remove this line if Doctrine Migrations is installed 58 | run: docker compose exec -T php bin/console -e test doctrine:migrations:migrate --no-interaction 59 | - 60 | name: Run PHPUnit 61 | if: false # Remove this line if PHPUnit is installed 62 | run: docker compose exec -T php bin/phpunit 63 | - 64 | name: Doctrine Schema Validator 65 | if: false # Remove this line if Doctrine ORM is installed 66 | run: docker compose exec -T php bin/console -e test doctrine:schema:validate 67 | lint: 68 | name: Docker Lint 69 | runs-on: ubuntu-latest 70 | steps: 71 | - 72 | name: Checkout 73 | uses: actions/checkout@v4 74 | - 75 | name: Lint Dockerfile 76 | uses: hadolint/hadolint-action@v3.1.0 77 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #syntax=docker/dockerfile:1 2 | 3 | # Versions 4 | FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream 5 | 6 | # The different stages of this Dockerfile are meant to be built into separate images 7 | # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage 8 | # https://docs.docker.com/compose/compose-file/#target 9 | 10 | 11 | # Base FrankenPHP image 12 | FROM frankenphp_upstream AS frankenphp_base 13 | 14 | WORKDIR /app 15 | 16 | VOLUME /app/var/ 17 | 18 | # persistent / runtime deps 19 | # hadolint ignore=DL3008 20 | RUN apt-get update && apt-get install -y --no-install-recommends \ 21 | acl \ 22 | file \ 23 | gettext \ 24 | git \ 25 | && rm -rf /var/lib/apt/lists/* 26 | 27 | RUN set -eux; \ 28 | install-php-extensions \ 29 | @composer \ 30 | apcu \ 31 | intl \ 32 | opcache \ 33 | zip \ 34 | ; 35 | 36 | # https://getcomposer.org/doc/03-cli.md#composer-allow-superuser 37 | ENV COMPOSER_ALLOW_SUPERUSER=1 38 | 39 | # Transport to use by Mercure (default to Bolt) 40 | ENV MERCURE_TRANSPORT_URL=bolt:///data/mercure.db 41 | 42 | ENV PHP_INI_SCAN_DIR=":$PHP_INI_DIR/app.conf.d" 43 | 44 | ###> recipes ### 45 | ###< recipes ### 46 | 47 | COPY --link frankenphp/conf.d/10-app.ini $PHP_INI_DIR/app.conf.d/ 48 | COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint 49 | COPY --link frankenphp/Caddyfile /etc/frankenphp/Caddyfile 50 | 51 | ENTRYPOINT ["docker-entrypoint"] 52 | 53 | HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1 54 | CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ] 55 | 56 | # Dev FrankenPHP image 57 | FROM frankenphp_base AS frankenphp_dev 58 | 59 | ENV APP_ENV=dev 60 | ENV XDEBUG_MODE=off 61 | ENV FRANKENPHP_WORKER_CONFIG=watch 62 | 63 | RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" 64 | 65 | RUN set -eux; \ 66 | install-php-extensions \ 67 | xdebug \ 68 | ; 69 | 70 | COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/ 71 | 72 | CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ] 73 | 74 | # Prod FrankenPHP image 75 | FROM frankenphp_base AS frankenphp_prod 76 | 77 | ENV APP_ENV=prod 78 | 79 | RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" 80 | 81 | COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/ 82 | 83 | # prevent the reinstallation of vendors at every changes in the source code 84 | COPY --link composer.* symfony.* ./ 85 | RUN set -eux; \ 86 | composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress 87 | 88 | # copy sources 89 | COPY --link . ./ 90 | RUN rm -Rf frankenphp/ 91 | 92 | RUN set -eux; \ 93 | mkdir -p var/cache var/log; \ 94 | composer dump-autoload --classmap-authoritative --no-dev; \ 95 | composer dump-env prod; \ 96 | composer run-script --no-dev post-install-cmd; \ 97 | chmod +x bin/console; sync; 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symfony Docker 2 | 3 | A [Docker](https://www.docker.com/)-based installer and runtime for the [Symfony](https://symfony.com) web framework, 4 | with [FrankenPHP](https://frankenphp.dev) and [Caddy](https://caddyserver.com/) inside! 5 | 6 | ![CI](https://github.com/dunglas/symfony-docker/workflows/CI/badge.svg) 7 | 8 | ## Getting Started 9 | 10 | 1. If not already done, [install Docker Compose](https://docs.docker.com/compose/install/) (v2.10+) 11 | 2. Run `docker compose build --pull --no-cache` to build fresh images 12 | 3. Run `docker compose up --wait` to set up and start a fresh Symfony project 13 | 4. Open `https://localhost` in your favorite web browser and [accept the auto-generated TLS certificate](https://stackoverflow.com/a/15076602/1352334) 14 | 5. Run `docker compose down --remove-orphans` to stop the Docker containers. 15 | 16 | ## Features 17 | 18 | * Production, development and CI ready 19 | * Just 1 service by default 20 | * Blazing-fast performance thanks to [the worker mode of FrankenPHP](https://github.com/dunglas/frankenphp/blob/main/docs/worker.md) (automatically enabled in prod mode) 21 | * [Installation of extra Docker Compose services](docs/extra-services.md) with Symfony Flex 22 | * Automatic HTTPS (in dev and prod) 23 | * HTTP/3 and [Early Hints](https://symfony.com/blog/new-in-symfony-6-3-early-hints) support 24 | * Real-time messaging thanks to a built-in [Mercure hub](https://symfony.com/doc/current/mercure.html) 25 | * [Vulcain](https://vulcain.rocks) support 26 | * Native [XDebug](docs/xdebug.md) integration 27 | * Super-readable configuration 28 | 29 | **Enjoy!** 30 | 31 | ## Docs 32 | 33 | 1. [Options available](docs/options.md) 34 | 2. [Using Symfony Docker with an existing project](docs/existing-project.md) 35 | 3. [Support for extra services](docs/extra-services.md) 36 | 4. [Deploying in production](docs/production.md) 37 | 5. [Debugging with Xdebug](docs/xdebug.md) 38 | 6. [TLS Certificates](docs/tls.md) 39 | 7. [Using MySQL instead of PostgreSQL](docs/mysql.md) 40 | 8. [Using Alpine Linux instead of Debian](docs/alpine.md) 41 | 9. [Using a Makefile](docs/makefile.md) 42 | 10. [Updating the template](docs/updating.md) 43 | 11. [Troubleshooting](docs/troubleshooting.md) 44 | 45 | ## License 46 | 47 | Symfony Docker is available under the MIT License. 48 | 49 | ## Credits 50 | 51 | Created by [Kévin Dunglas](https://dunglas.dev), co-maintained by [Maxime Helias](https://twitter.com/maxhelias) and sponsored by [Les-Tilleuls.coop](https://les-tilleuls.coop). 52 | -------------------------------------------------------------------------------- /compose.override.yaml: -------------------------------------------------------------------------------- 1 | # Development environment override 2 | services: 3 | php: 4 | build: 5 | context: . 6 | target: frankenphp_dev 7 | volumes: 8 | - ./:/app 9 | - ./frankenphp/Caddyfile:/etc/frankenphp/Caddyfile:ro 10 | - ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro 11 | # If you develop on Mac or Windows you can remove the vendor/ directory 12 | # from the bind-mount for better performance by enabling the next line: 13 | #- /app/vendor 14 | environment: 15 | FRANKENPHP_WORKER_CONFIG: watch 16 | MERCURE_EXTRA_DIRECTIVES: demo 17 | # See https://xdebug.org/docs/all_settings#mode 18 | XDEBUG_MODE: "${XDEBUG_MODE:-off}" 19 | APP_ENV: "${APP_ENV:-dev}" 20 | extra_hosts: 21 | # Ensure that host.docker.internal is correctly defined on Linux 22 | - host.docker.internal:host-gateway 23 | tty: true 24 | 25 | ###> symfony/mercure-bundle ### 26 | ###< symfony/mercure-bundle ### 27 | -------------------------------------------------------------------------------- /compose.prod.yaml: -------------------------------------------------------------------------------- 1 | # Production environment override 2 | services: 3 | php: 4 | build: 5 | context: . 6 | target: frankenphp_prod 7 | environment: 8 | APP_SECRET: ${APP_SECRET} 9 | MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} 10 | MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} 11 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | php: 3 | image: ${IMAGES_PREFIX:-}app-php 4 | restart: unless-stopped 5 | environment: 6 | SERVER_NAME: ${SERVER_NAME:-localhost}, php:80 7 | MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} 8 | MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} 9 | # Run "composer require symfony/orm-pack" to install and configure Doctrine ORM 10 | DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8} 11 | # Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration 12 | MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure} 13 | MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}:${HTTPS_PORT:-443}/.well-known/mercure} 14 | MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} 15 | # The two next lines can be removed after initial installation 16 | SYMFONY_VERSION: ${SYMFONY_VERSION:-} 17 | STABILITY: ${STABILITY:-stable} 18 | volumes: 19 | - caddy_data:/data 20 | - caddy_config:/config 21 | ports: 22 | # HTTP 23 | - target: 80 24 | published: ${HTTP_PORT:-80} 25 | protocol: tcp 26 | # HTTPS 27 | - target: 443 28 | published: ${HTTPS_PORT:-443} 29 | protocol: tcp 30 | # HTTP/3 31 | - target: 443 32 | published: ${HTTP3_PORT:-443} 33 | protocol: udp 34 | 35 | # Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service 36 | ###> symfony/mercure-bundle ### 37 | ###< symfony/mercure-bundle ### 38 | 39 | volumes: 40 | caddy_data: 41 | caddy_config: 42 | ###> symfony/mercure-bundle ### 43 | ###< symfony/mercure-bundle ### 44 | -------------------------------------------------------------------------------- /docs/alpine.md: -------------------------------------------------------------------------------- 1 | # Using Alpine Linux Instead of Debian 2 | 3 | By default, Symfony Docker uses Debian-based FrankenPHP Docker images. 4 | This is the recommended solution. 5 | 6 | Alternatively, it's possible to use Alpine-based images, which are smaller but 7 | are known to be slower, and have several known issues. 8 | 9 | To switch to Alpine-based images, apply the following changes to the `Dockerfile`: 10 | 11 | ```patch 12 | -FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream 13 | +FROM dunglas/frankenphp:1-php8.3-alpine AS frankenphp_upstream 14 | 15 | -# hadolint ignore=DL3008 16 | -RUN apt-get update && apt-get install -y --no-install-recommends \ 17 | - acl \ 18 | - file \ 19 | - gettext \ 20 | - git \ 21 | - && rm -rf /var/lib/apt/lists/* 22 | +# hadolint ignore=DL3018 23 | +RUN apk add --no-cache \ 24 | + acl \ 25 | + file \ 26 | + gettext \ 27 | + git \ 28 | + ; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/digitalocean-dns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/symfony-docker/6884829b0b62e021d8dd0d7cd154399135f83e79/docs/digitalocean-dns.png -------------------------------------------------------------------------------- /docs/digitalocean-droplet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/symfony-docker/6884829b0b62e021d8dd0d7cd154399135f83e79/docs/digitalocean-droplet.png -------------------------------------------------------------------------------- /docs/existing-project.md: -------------------------------------------------------------------------------- 1 | # Installing on an Existing Project 2 | 3 | It's also possible to use Symfony Docker with existing projects! 4 | 5 | First, [download this skeleton](https://github.com/dunglas/symfony-docker). 6 | 7 | If you cloned the Git repository, be sure to not copy the `.git` directory to prevent conflicts with the `.git` directory already in your existing project. 8 | You can copy the contents of the repository using git and tar. This will not contain `.git` or any uncommited changes. 9 | 10 | git archive --format=tar HEAD | tar -xC my-existing-project/ 11 | 12 | If you downloaded the skeleton as a zip you can just copy the extracted files: 13 | 14 | cp -Rp symfony-docker/. my-existing-project/ 15 | 16 | Enable the Docker support of Symfony Flex: 17 | 18 | composer config --json extra.symfony.docker 'true' 19 | 20 | Re-execute the recipes to update the Docker-related files according to the packages you use 21 | 22 | rm symfony.lock 23 | composer recipes:install --force --verbose 24 | 25 | Double-check the changes, revert the changes that you don't want to keep: 26 | 27 | git diff 28 | ... 29 | 30 | Build the Docker images: 31 | 32 | docker compose build --pull --no-cache 33 | 34 | Start the project! 35 | 36 | docker compose up --wait 37 | 38 | Browse `https://localhost`, your Docker configuration is ready! 39 | 40 | > [!NOTE] 41 | > If you want to use the worker mode of FrankenPHP, make sure you required the `runtime/frankenphp-symfony` package. 42 | 43 | > [!NOTE] 44 | > The worker mode of FrankenPHP is enabled by default in prod. To disabled it, add the env var FRANKENPHP_CONFIG as empty to the compose.prod.yaml file. 45 | -------------------------------------------------------------------------------- /docs/extra-services.md: -------------------------------------------------------------------------------- 1 | # Support for Extra Services 2 | 3 | Symfony Docker is extensible. When you install a compatible Composer package using Symfony Flex, 4 | the recipe will automatically modify the `Dockerfile` and `compose.yaml` to fulfill the requirements of this package. 5 | 6 | The currently supported packages are: 7 | 8 | * `symfony/orm-pack`: install a PostgreSQL service 9 | * `symfony/mercure-bundle`: use the Mercure.rocks module shipped with Caddy 10 | * `symfony/panther`: install chromium and these drivers 11 | * `symfony/mailer`: install a Mailpit service 12 | * `blackfireio/blackfire-symfony-meta`: install a Blackfire service 13 | 14 | > [!NOTE] 15 | > If a recipe modifies the Dockerfile, the container needs to be rebuilt. 16 | 17 | > [!WARNING] 18 | > We recommend that you use the `composer require` command inside the container in development mode so that recipes can be applied correctly 19 | -------------------------------------------------------------------------------- /docs/makefile.md: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | Here is a Makefile template. It provides some shortcuts for the most common tasks. 4 | To use it, create a new `Makefile` file at the root of your project. Copy/paste 5 | the content in the template section. To view all the available commands, run `make`. 6 | 7 | For example, in the [getting started section](/README.md#getting-started), the 8 | `docker compose` commands could be replaced by: 9 | 10 | 1. Run `make build` to build fresh images 11 | 2. Run `make up` (detached mode without logs) 12 | 3. Run `make down` to stop the Docker containers 13 | 14 | Of course, this template is basic for now. But, as your application is growing, 15 | you will probably want to add some targets like running your tests as described 16 | in [the Symfony book](https://symfony.com/doc/current/the-fast-track/en/17-tests.html#automating-your-workflow-with-a-makefile). 17 | You can also find a more complete example in this [snippet](https://www.strangebuzz.com/en/snippets/the-perfect-makefile-for-symfony). 18 | 19 | If you want to run make from within the `php` container, in the [Dockerfile](/Dockerfile), 20 | add: 21 | 22 | ```diff 23 | gettext \ 24 | git \ 25 | +make \ 26 | ``` 27 | 28 | And rebuild the PHP image. 29 | 30 | > [!NOTE] 31 | > If you are using Windows, you have to install [chocolatey.org](https://chocolatey.org/) or [Cygwin](http://cygwin.com) to use the `make` command. Check out this [StackOverflow question](https://stackoverflow.com/q/2532234/633864) for more explanations. 32 | 33 | ## The template 34 | 35 | ```Makefile 36 | # Executables (local) 37 | DOCKER_COMP = docker compose 38 | 39 | # Docker containers 40 | PHP_CONT = $(DOCKER_COMP) exec php 41 | 42 | # Executables 43 | PHP = $(PHP_CONT) php 44 | COMPOSER = $(PHP_CONT) composer 45 | SYMFONY = $(PHP) bin/console 46 | 47 | # Misc 48 | .DEFAULT_GOAL = help 49 | .PHONY : help build up start down logs sh composer vendor sf cc test 50 | 51 | ## —— 🎵 🐳 The Symfony Docker Makefile 🐳 🎵 —————————————————————————————————— 52 | help: ## Outputs this help screen 53 | @grep -E '(^[a-zA-Z0-9\./_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' 54 | 55 | ## —— Docker 🐳 ———————————————————————————————————————————————————————————————— 56 | build: ## Builds the Docker images 57 | @$(DOCKER_COMP) build --pull --no-cache 58 | 59 | up: ## Start the docker hub in detached mode (no logs) 60 | @$(DOCKER_COMP) up --detach 61 | 62 | start: build up ## Build and start the containers 63 | 64 | down: ## Stop the docker hub 65 | @$(DOCKER_COMP) down --remove-orphans 66 | 67 | logs: ## Show live logs 68 | @$(DOCKER_COMP) logs --tail=0 --follow 69 | 70 | sh: ## Connect to the FrankenPHP container 71 | @$(PHP_CONT) sh 72 | 73 | bash: ## Connect to the FrankenPHP container via bash so up and down arrows go to previous commands 74 | @$(PHP_CONT) bash 75 | 76 | test: ## Start tests with phpunit, pass the parameter "c=" to add options to phpunit, example: make test c="--group e2e --stop-on-failure" 77 | @$(eval c ?=) 78 | @$(DOCKER_COMP) exec -e APP_ENV=test php bin/phpunit $(c) 79 | 80 | 81 | ## —— Composer 🧙 —————————————————————————————————————————————————————————————— 82 | composer: ## Run composer, pass the parameter "c=" to run a given command, example: make composer c='req symfony/orm-pack' 83 | @$(eval c ?=) 84 | @$(COMPOSER) $(c) 85 | 86 | vendor: ## Install vendors according to the current composer.lock file 87 | vendor: c=install --prefer-dist --no-dev --no-progress --no-scripts --no-interaction 88 | vendor: composer 89 | 90 | ## —— Symfony 🎵 ——————————————————————————————————————————————————————————————— 91 | sf: ## List all Symfony commands or pass the parameter "c=" to run a given command, example: make sf c=about 92 | @$(eval c ?=) 93 | @$(SYMFONY) $(c) 94 | 95 | cc: c=c:c ## Clear the cache 96 | cc: sf 97 | ``` 98 | 99 | ## Adding and modifying jobs 100 | 101 | Make sure to add this configuration to the [.editorconfig](/.editorconfig) file, so that it forces your editor to use tabs instead of spaces (Makefiles are not compatible with spaces by default). 102 | 103 | ```.editorconfig 104 | 105 | [Makefile] 106 | indent_style = tab 107 | 108 | ``` 109 | 110 | If you still want to use space, you can configure the prefix in the Makefile itself. See [this answer on StackExchange](https://retrocomputing.stackexchange.com/a/20303). -------------------------------------------------------------------------------- /docs/mysql.md: -------------------------------------------------------------------------------- 1 | # Using MySQL 2 | 3 | The Docker configuration of this repository is extensible thanks to Flex recipes. By default, the recipe installs PostgreSQL. 4 | If you prefer to work with MySQL, follow these steps: 5 | 6 | First, install the `symfony/orm-pack` package as described: `docker compose exec php composer req symfony/orm-pack` 7 | 8 | ## Docker Configuration 9 | Change the database image to use MySQL instead of PostgreSQL in `compose.yaml`: 10 | 11 | ```diff 12 | ###> doctrine/doctrine-bundle ### 13 | - image: postgres:${POSTGRES_VERSION:-16}-alpine 14 | + image: mysql:${MYSQL_VERSION:-8} 15 | environment: 16 | - POSTGRES_DB: ${POSTGRES_DB:-app} 17 | + MYSQL_DATABASE: ${MYSQL_DATABASE:-app} 18 | # You should definitely change the password in production 19 | + MYSQL_RANDOM_ROOT_PASSWORD: "true" 20 | - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} 21 | + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-!ChangeMe!} 22 | - POSTGRES_USER: ${POSTGRES_USER:-app} 23 | + MYSQL_USER: ${MYSQL_USER:-app} 24 | healthcheck: 25 | - test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"] 26 | + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] 27 | timeout: 5s 28 | retries: 5 29 | start_period: 60s 30 | volumes: 31 | - - database_data:/var/lib/postgresql/data:rw 32 | + - database_data:/var/lib/mysql:rw 33 | # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! 34 | - # - ./docker/db/data:/var/lib/postgresql/data:rw 35 | + # - ./docker/db/data:/var/lib/mysql:rw 36 | ###< doctrine/doctrine-bundle ### 37 | ``` 38 | 39 | Depending on the database configuration, modify the environment in the same file at `services.php.environment.DATABASE_URL` 40 | ``` 41 | DATABASE_URL: mysql://${MYSQL_USER:-app}:${MYSQL_PASSWORD:-!ChangeMe!}@database:3306/${MYSQL_DATABASE:-app}?serverVersion=${MYSQL_VERSION:-8}&charset=${MYSQL_CHARSET:-utf8mb4} 42 | ``` 43 | 44 | Since we changed the port, we also have to define this in the `compose.override.yaml`: 45 | ```diff 46 | ###> doctrine/doctrine-bundle ### 47 | database: 48 | ports: 49 | - - "5432" 50 | + - "3306" 51 | ###< doctrine/doctrine-bundle ### 52 | ``` 53 | 54 | Last but not least, we need to install the MySQL driver in `Dockerfile`: 55 | ```diff 56 | ###> doctrine/doctrine-bundle ### 57 | -RUN install-php-extensions pdo_pgsql 58 | +RUN install-php-extensions pdo_mysql 59 | ###< doctrine/doctrine-bundle ### 60 | ``` 61 | 62 | ## Change Environment 63 | Change the database configuration in `.env`: 64 | 65 | ```dotenv 66 | DATABASE_URL=mysql://${MYSQL_USER:-app}:${MYSQL_PASSWORD:-!ChangeMe!}@database:3306/${MYSQL_DATABASE:-app}?serverVersion=${MYSQL_VERSION:-8}&charset=${MYSQL_CHARSET:-utf8mb4} 67 | ``` 68 | 69 | ## Final steps 70 | Rebuild the docker environment: 71 | ```shell 72 | docker compose down --remove-orphans && docker compose build --pull --no-cache 73 | ``` 74 | 75 | Start the services: 76 | ```shell 77 | docker compose up --wait 78 | ``` 79 | 80 | Test your setup: 81 | ```shell 82 | docker compose exec php bin/console dbal:run-sql -q "SELECT 1" && echo "OK" || echo "Connection is not working" 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/options.md: -------------------------------------------------------------------------------- 1 | # Docker Build Options 2 | 3 | You can customize the docker build process using these environment variables. 4 | 5 | > [!NOTE] 6 | > All Symfony-specific environment variables are used only if no `composer.json` file is found in the project directory. 7 | 8 | ## Selecting a Specific Symfony Version 9 | 10 | Use the `SYMFONY_VERSION` environment variable to select a specific Symfony version. 11 | 12 | For instance, use the following command to install Symfony 6.4: 13 | 14 | On Linux: 15 | 16 | SYMFONY_VERSION=6.4.* docker compose up --wait 17 | On Windows: 18 | 19 | set SYMFONY_VERSION=6.4.* && docker compose up --wait&set SYMFONY_VERSION= 20 | 21 | ## Installing Development Versions of Symfony 22 | 23 | To install a non-stable version of Symfony, use the `STABILITY` environment variable during the build. 24 | The value must be [a valid Composer stability option](https://getcomposer.org/doc/04-schema.md#minimum-stability). 25 | 26 | For instance, use the following command to use the development branch of Symfony: 27 | 28 | On Linux: 29 | 30 | STABILITY=dev docker compose up --wait 31 | 32 | On Windows: 33 | 34 | set STABILITY=dev && docker compose up --wait&set STABILITY= 35 | 36 | ## Using custom HTTP ports 37 | 38 | Use the environment variables `HTTP_PORT`, `HTTPS_PORT` and/or `HTTP3_PORT` to adjust the ports to your needs, e.g. 39 | 40 | HTTP_PORT=8000 HTTPS_PORT=4443 HTTP3_PORT=4443 docker compose up --wait 41 | 42 | to access your application on [https://localhost:4443](https://localhost:4443). 43 | 44 | > [!NOTE] 45 | > Let's Encrypt only supports the standard HTTP and HTTPS ports. Creating a Let's Encrypt certificate for another port will not work, you have to use the standard ports or to configure Caddy to use another provider. 46 | 47 | 48 | ## Caddyfile Options 49 | 50 | You can also customize the `Caddyfile` by using the following environment variables to inject options block, directive or configuration. 51 | 52 | > [!TIP] 53 | > All the following environment variables can be defined in your `.env` file at the root of the project to keep them persistent at each startup 54 | 55 | | Environment variable | Description | Default value | 56 | |---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------| 57 | | `CADDY_GLOBAL_OPTIONS` | the [global options block](https://caddyserver.com/docs/caddyfile/options#global-options), one per line | | 58 | | `CADDY_EXTRA_CONFIG` | the [snippet](https://caddyserver.com/docs/caddyfile/concepts#snippets) or the [named-routes](https://caddyserver.com/docs/caddyfile/concepts#named-routes) options block, one per line | | 59 | | `CADDY_SERVER_EXTRA_DIRECTIVES` | the [`Caddyfile` directives](https://caddyserver.com/docs/caddyfile/concepts#directives) | | 60 | | `CADDY_SERVER_LOG_OPTIONS` | the [server log options block](https://caddyserver.com/docs/caddyfile/directives/log), one per line | | 61 | | `SERVER_NAME` | the server name or address | `localhost` | 62 | | `FRANKENPHP_CONFIG` | a list of extra [FrankenPHP directives](https://frankenphp.dev/docs/config/#caddyfile-config), one per line | `import worker.Caddyfile` | 63 | | `MERCURE_TRANSPORT_URL` | the value passed to the `transport_url` directive | `bolt:///data/mercure.db` | 64 | | `MERCURE_PUBLISHER_JWT_KEY` | the JWT key to use for publishers | | 65 | | `MERCURE_PUBLISHER_JWT_ALG` | the JWT algorithm to use for publishers | `HS256` | 66 | | `MERCURE_SUBSCRIBER_JWT_KEY` | the JWT key to use for subscribers | | 67 | | `MERCURE_SUBSCRIBER_JWT_ALG` | the JWT algorithm to use for subscribers | `HS256` | 68 | | `MERCURE_EXTRA_DIRECTIVES` | a list of extra [Mercure directives](https://mercure.rocks/docs/hub/config), one per line | | 69 | 70 | ### Example of server name customize: 71 | 72 | SERVER_NAME="app.localhost" docker compose up --wait 73 | -------------------------------------------------------------------------------- /docs/production.md: -------------------------------------------------------------------------------- 1 | # Deploying in Production 2 | 3 | Symfony Docker provides Docker images, and a Docker Compose definition optimized for production usage. 4 | In this tutorial, we will learn how to deploy our Symfony application on a single server using Docker Compose. 5 | 6 | ## Preparing a Server 7 | 8 | To deploy your application in production, you need a server. 9 | In this tutorial, we will use a virtual machine provided by DigitalOcean, but any Linux server can work. 10 | If you already have a Linux server with Docker Compose installed, you can skip straight to [the next section](#configuring-a-domain-name). 11 | 12 | Otherwise, use [this affiliate link](https://m.do.co/c/5d8aabe3ab80) to get $100 of free credit, create an account, then click on "Create a Droplet". 13 | Then, click on the "Marketplace" tab under the "Choose an image" section and search for the app named "Docker". 14 | This will provision an Ubuntu server with the latest versions of Docker and Docker Compose already installed! 15 | 16 | For test purposes, the cheapest plans will be enough, even though you might want at least 2GB of RAM to execute Docker Compose for the first time. For real production usage, you'll probably want to pick a plan in the "general purpose" section to fit your needs. 17 | 18 | ![Deploying a Symfony app on DigitalOcean with Docker Compose](digitalocean-droplet.png) 19 | 20 | You can keep the defaults for other settings, or tweak them according to your needs. 21 | Don't forget to add your SSH key or create a password then press the "Finalize and create" button. 22 | 23 | Then, wait a few seconds while your Droplet is provisioning. 24 | When your Droplet is ready, use SSH to connect: 25 | 26 | ```console 27 | ssh root@ 28 | ``` 29 | 30 | ## Configuring a Domain Name 31 | 32 | In most cases, you'll want to associate a domain name with your site. 33 | If you don't own a domain name yet, you'll have to buy one through a registrar. 34 | 35 | Then create a DNS record of type `A` for your domain name pointing to the IP address of your server: 36 | 37 | ```dns 38 | your-domain-name.example.com. IN A 207.154.233.113 39 | ``` 40 | 41 | Example with the DigitalOcean Domains service ("Networking" > "Domains"): 42 | 43 | ![Configuring DNS on DigitalOcean](digitalocean-dns.png) 44 | 45 | > [!NOTE] 46 | > Let's Encrypt, the service used by default by Symfony Docker to automatically generate a TLS certificate doesn't support using bare IP addresses. Using a domain name is mandatory to use Let's Encrypt. 47 | 48 | ## Deploying 49 | 50 | Copy your project on the server using `git clone`, `scp`, or any other tool that may fit your need. 51 | If you use GitHub, you may want to use [a deploy key](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys). 52 | Deploy keys are also [supported by GitLab](https://docs.gitlab.com/ee/user/project/deploy_keys/). 53 | 54 | Example with Git: 55 | 56 | ```console 57 | git clone git@github.com:/.git 58 | ``` 59 | 60 | Go into the directory containing your project (``), and start the app in production mode: 61 | 62 | ```console 63 | # Build fresh production image 64 | docker compose -f compose.yaml -f compose.prod.yaml build --pull --no-cache 65 | 66 | # Start container 67 | SERVER_NAME=your-domain-name.example.com \ 68 | APP_SECRET=ChangeMe \ 69 | CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \ 70 | docker compose -f compose.yaml -f compose.prod.yaml up --wait 71 | ``` 72 | 73 | Be sure to replace `your-domain-name.example.com` with your actual domain name and to set the values of `APP_SECRET`, `CADDY_MERCURE_JWT_SECRET` to cryptographically secure random values. 74 | 75 | Your server is up and running, and a HTTPS certificate has been automatically generated for you. 76 | Go to `https://your-domain-name.example.com` and enjoy! 77 | 78 | > [!NOTE] 79 | > The worker mode of FrankenPHP is enabled by default in prod. To disable it, add the env var FRANKENPHP_CONFIG as empty to the compose.prod.yaml file. 80 | 81 | > [!CAUTION] 82 | > Docker can have a cache layer, make sure you have the right build for each deployment or rebuild your project with --no-cache option to avoid cache issue. 83 | 84 | ## Disabling HTTPS 85 | 86 | Alternatively, if you don't want to expose an HTTPS server but only an HTTP one, run the following command: 87 | 88 | ```console 89 | SERVER_NAME=:80 \ 90 | APP_SECRET=ChangeMe \ 91 | CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \ 92 | docker compose -f compose.yaml -f compose.prod.yaml up --wait 93 | ``` 94 | 95 | ## Deploying on Multiple Nodes 96 | 97 | If you want to deploy your app on a cluster of machines, you can use [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), 98 | which is compatible with the provided Compose files. 99 | To deploy on Kubernetes, take a look at [the Helm chart provided with API Platform](https://api-platform.com/docs/deployment/kubernetes/), which can be easily adapted for use with Symfony Docker. 100 | 101 | ## Passing local environment variables to containers 102 | 103 | By default, `.env.local` and `.env.*.local` files are excluded from production images. 104 | If you want to pass them to your containers, you can use the [`env_file` attribute](https://docs.docker.com/compose/environment-variables/set-environment-variables/#use-the-env_file-attribute): 105 | 106 | ```yaml 107 | # compose.prod.yml 108 | 109 | services: 110 | php: 111 | env_file: 112 | - .env.prod.local 113 | # ... 114 | ``` 115 | -------------------------------------------------------------------------------- /docs/tls.md: -------------------------------------------------------------------------------- 1 | # TLS Certificates 2 | 3 | ## Trusting the Authority 4 | 5 | With a standard installation, the authority used to sign certificates generated in the Caddy container is not trusted by your local machine. 6 | You must add the authority to the trust store of the host : 7 | 8 | ``` 9 | # Mac 10 | $ docker cp $(docker compose ps -q php):/data/caddy/pki/authorities/local/root.crt /tmp/root.crt && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/root.crt 11 | # Linux 12 | $ docker cp $(docker compose ps -q php):/data/caddy/pki/authorities/local/root.crt /usr/local/share/ca-certificates/root.crt && sudo update-ca-certificates 13 | # Windows 14 | $ docker compose cp php:/data/caddy/pki/authorities/local/root.crt %TEMP%/root.crt && certutil -addstore -f "ROOT" %TEMP%/root.crt 15 | ``` 16 | 17 | ## Using Custom TLS Certificates 18 | 19 | By default, Caddy will automatically generate TLS certificates using Let's Encrypt or ZeroSSL. 20 | But sometimes you may prefer using custom certificates. 21 | 22 | For instance, to use self-signed certificates created with [mkcert](https://github.com/FiloSottile/mkcert) do as follows: 23 | 24 | 1. Locally install `mkcert` 25 | 2. Create the folder storing the certs: 26 | `mkdir -p frankenphp/certs` 27 | 3. Generate the certificates for your local host (example: "server-name.localhost"): 28 | `mkcert -cert-file frankenphp/certs/tls.pem -key-file frankenphp/certs/tls.key "server-name.localhost"` 29 | 4. Add these lines to the `./compose.override.yaml` file about `CADDY_SERVER_EXTRA_DIRECTIVES` environment and volume for the `php` service : 30 | ```diff 31 | php: 32 | environment: 33 | + CADDY_SERVER_EXTRA_DIRECTIVES: "tls /etc/caddy/certs/tls.pem /etc/caddy/certs/tls.key" 34 | # ... 35 | volumes: 36 | + - ./frankenphp/certs:/etc/caddy/certs:ro 37 | # ... 38 | ``` 39 | 5. Restart your `php` service 40 | 41 | ## Disabling HTTPS for Local Development 42 | 43 | To disable HTTPS, configure your environment to use HTTP by setting the following variables and starting the project with this command: 44 | 45 | ```bash 46 | SERVER_NAME=http://localhost \ 47 | MERCURE_PUBLIC_URL=http://localhost/.well-known/mercure \ 48 | docker compose up --wait 49 | ``` 50 | 51 | Ensure your application is accessible over HTTP by visiting `http://localhost` in your web browser. 52 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | ## Editing Permissions on Linux 4 | 5 | If you work on linux and cannot edit some of the project files right after the first installation, you can run `docker compose run --rm php chown -R $(id -u):$(id -g) .` to set yourself as owner of the project files that were created by the docker container. 6 | 7 | ## TLS/HTTPS Issues 8 | 9 | See more in the [TLS section](tls.md) 10 | 11 | ## Production issues 12 | 13 | ### How to properly build fresh images for production use 14 | 15 | Remember that, by default, if you run `docker compose up --wait`, only the files `compose.yaml` and `compose.override.yaml` will be used. 16 | See https://docs.docker.com/compose/intro/compose-application-model and https://docs.docker.com/compose/how-tos/multiple-compose-files/merge. 17 | 18 | If you need to build images for production environment, you have to use the following command: 19 | 20 | ```console 21 | docker compose -f compose.yaml -f compose.prod.yaml build --pull --no-cache 22 | ``` 23 | 24 | ### Why application outputs `phpinfo()` 25 | 26 | Both dev and prod images have the same image tag (`<...>app-php:latest`). This can cause confusion when working with images. 27 | It is important to make sure that your image is the appropriate one for the current environment. 28 | 29 | If you are not careful about this, and try to run your production container(s) with 30 | `docker compose -f compose.yaml -f compose.prod.yaml up --wait` 31 | without the right build process beforehand, your application **will still launch**, but will be displaying an output of `phpinfo()` (or possibly even a HTTP 500 error page). 32 | 33 | See details below. 34 | 35 |
36 | 37 | Output of a basic build process 38 | 39 | In the case of a dev image, you need the `compose.yaml` and `compose.override.yaml` files. Which are the default files for Docker Compose. 40 | This means that running `docker compose ` or `docker compose -f compose.yaml -f compose.override.yaml ` is the same thing. 41 | 42 | And in doing so, images `frankenphp_base` and `frankenphp_dev` are built. And not `frankenphp_prod`. 43 | Which is good enough for dev purposes. 44 | 45 | Then, you can start your dev container(s) by running: 46 | 47 | ```console 48 | docker compose up --wait 49 | ``` 50 | 51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 | Output expected for the production build process 59 | 60 | To build the production image, you have to specify the `compose.yaml` and `compose.prod.yaml` files. 61 | This means you have to run: `docker compose -f compose.yaml -f compose.prod.yaml build --pull --no-cache` in order to build your image 62 | (careful: the order of `-f` arguments is important). 63 | 64 | That way, you will see that `frankenphp_base` and `frankenphp_prod` are built this time, which is what you will need for production purposes. 65 | 66 | You can finally start your prod container(s) by running: 67 | 68 | ```console 69 | docker compose -f compose.yaml -f compose.prod.yaml up --wait 70 | ``` 71 | 72 |
73 | 74 | -------------------------------------------------------------------------------- /docs/updating.md: -------------------------------------------------------------------------------- 1 | # Updating Your Project 2 | 3 | To import the changes made to the *Symfony Docker* template into your project, we recommend using 4 | [*template-sync*](https://github.com/coopTilleuls/template-sync): 5 | 6 | 1. Run the script to synchronize your project with the latest version of the skeleton: 7 | 8 | ```console 9 | curl -sSL https://raw.githubusercontent.com/coopTilleuls/template-sync/main/template-sync.sh | sh -s -- https://github.com/dunglas/symfony-docker 10 | ``` 11 | 12 | 2. Resolve conflicts, if any 13 | 3. Run `git cherry-pick --continue` 14 | 15 | For more advanced options, refer to [the documentation of *template sync*](https://github.com/coopTilleuls/template-sync#template-sync). 16 | -------------------------------------------------------------------------------- /docs/xdebug.md: -------------------------------------------------------------------------------- 1 | # Using Xdebug 2 | 3 | The default development image is shipped with [Xdebug](https://xdebug.org/), 4 | a popular debugger and profiler for PHP. 5 | 6 | Because it has a significant performance overhead, the step-by-step debugger is disabled by default. 7 | It can be enabled by setting the `XDEBUG_MODE` environment variable to `debug`. 8 | 9 | On Linux and Mac: 10 | 11 | ``` 12 | XDEBUG_MODE=debug docker compose up --wait 13 | ``` 14 | 15 | On Windows: 16 | 17 | ``` 18 | set XDEBUG_MODE=debug&& docker compose up --wait&set XDEBUG_MODE= 19 | ``` 20 | 21 | ## Debugging with Xdebug and PHPStorm 22 | 23 | First, [create a PHP debug remote server configuration](https://www.jetbrains.com/help/phpstorm/creating-a-php-debug-server-configuration.html): 24 | 25 | 1. In the `Settings/Preferences` dialog, go to `PHP | Servers` 26 | 2. Create a new server: 27 | * Name: `symfony` (or whatever you want to use for the variable `PHP_IDE_CONFIG`) 28 | * Host: `localhost` (or the one defined using the `SERVER_NAME` environment variable) 29 | * Port: `443` 30 | * Debugger: `Xdebug` 31 | * Check `Use path mappings` 32 | * Absolute path on the server: `/app` 33 | 34 | You can now use the debugger! 35 | 36 | 1. In PHPStorm, open the `Run` menu and click on `Start Listening for PHP Debug Connections` 37 | 2. Add the `XDEBUG_SESSION=PHPSTORM` query parameter to the URL of the page you want to debug, or use [other available triggers](https://xdebug.org/docs/step_debug#activate_debugger) 38 | 39 | Alternatively, you can use [the **Xdebug extension**](https://xdebug.org/docs/step_debug#browser-extensions) for your preferred web browser. 40 | 41 | 3. On command line, we might need to tell PHPStorm which [path mapping configuration](https://www.jetbrains.com/help/phpstorm/zero-configuration-debugging-cli.html#configure-path-mappings) should be used, set the value of the PHP_IDE_CONFIG environment variable to `serverName=symfony`, where `symfony` is the name of the debug server configured higher. 42 | 43 | Example: 44 | 45 | ```console 46 | XDEBUG_SESSION=1 PHP_IDE_CONFIG="serverName=symfony" php bin/console ... 47 | ``` 48 | 49 | ## Debugging with Xdebug and VScode 50 | 51 | 1. Install necessary [PHP extension for VScode](https://marketplace.visualstudio.com/items?itemName=DEVSENSE.phptools-vscode). 52 | 2. Add [debug configuration](https://code.visualstudio.com/docs/debugtest/debugging-configuration#_launch-configurations) into your `.vscode\launch.json` file. 53 | 54 | Example: 55 | 56 | ``` 57 | { 58 | "version": "0.2.0", 59 | "configurations": [ 60 | { 61 | "name": "Listen for Xdebug", 62 | "type": "php", 63 | "request": "launch", 64 | "port": 9003, 65 | "pathMappings": { 66 | "/app": "${workspaceFolder}" 67 | } 68 | } 69 | ] 70 | } 71 | ``` 72 | 73 | 3. Use [Run and Debug](https://code.visualstudio.com/docs/debugtest/debugging#_start-a-debugging-session) options and run `Listen for Xdebug` command to listen for upcomming connections with [the **Xdebug extension**](https://xdebug.org/docs/step_debug#browser-extensions) installed and active. 74 | 75 | ## Troubleshooting 76 | 77 | Inspect the installation with the following command. The Xdebug version should be displayed. 78 | 79 | ```console 80 | $ docker compose exec php php --version 81 | 82 | PHP ... 83 | with Xdebug v3.x.x ... 84 | ``` 85 | -------------------------------------------------------------------------------- /frankenphp/Caddyfile: -------------------------------------------------------------------------------- 1 | { 2 | skip_install_trust 3 | 4 | {$CADDY_GLOBAL_OPTIONS} 5 | 6 | frankenphp { 7 | {$FRANKENPHP_CONFIG} 8 | 9 | worker { 10 | file ./public/index.php 11 | env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime 12 | {$FRANKENPHP_WORKER_CONFIG} 13 | } 14 | } 15 | } 16 | 17 | {$CADDY_EXTRA_CONFIG} 18 | 19 | {$SERVER_NAME:localhost} { 20 | log { 21 | {$CADDY_SERVER_LOG_OPTIONS} 22 | # Redact the authorization query parameter that can be set by Mercure 23 | format filter { 24 | request>uri query { 25 | replace authorization REDACTED 26 | } 27 | } 28 | } 29 | 30 | root /app/public 31 | encode zstd br gzip 32 | 33 | mercure { 34 | # Publisher JWT key 35 | publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} 36 | # Subscriber JWT key 37 | subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} 38 | # Allow anonymous subscribers (double-check that it's what you want) 39 | anonymous 40 | # Enable the subscription API (double-check that it's what you want) 41 | subscriptions 42 | # Extra directives 43 | {$MERCURE_EXTRA_DIRECTIVES} 44 | } 45 | 46 | vulcain 47 | 48 | {$CADDY_SERVER_EXTRA_DIRECTIVES} 49 | 50 | # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics 51 | header ?Permissions-Policy "browsing-topics=()" 52 | 53 | @phpRoute { 54 | not path /.well-known/mercure* 55 | not file {path} 56 | } 57 | rewrite @phpRoute index.php 58 | 59 | @frontController path index.php 60 | php @frontController 61 | 62 | file_server { 63 | hide *.php 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /frankenphp/conf.d/10-app.ini: -------------------------------------------------------------------------------- 1 | expose_php = 0 2 | date.timezone = UTC 3 | apc.enable_cli = 1 4 | session.use_strict_mode = 1 5 | zend.detect_unicode = 0 6 | 7 | ; https://symfony.com/doc/current/performance.html 8 | realpath_cache_size = 4096K 9 | realpath_cache_ttl = 600 10 | opcache.interned_strings_buffer = 16 11 | opcache.max_accelerated_files = 20000 12 | opcache.memory_consumption = 256 13 | opcache.enable_file_override = 1 14 | -------------------------------------------------------------------------------- /frankenphp/conf.d/20-app.dev.ini: -------------------------------------------------------------------------------- 1 | ; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host 2 | ; See https://github.com/docker/for-linux/issues/264 3 | ; The `client_host` below may optionally be replaced with `discover_client_host=yes` 4 | ; Add `start_with_request=yes` to start debug session on each request 5 | xdebug.client_host = host.docker.internal 6 | -------------------------------------------------------------------------------- /frankenphp/conf.d/20-app.prod.ini: -------------------------------------------------------------------------------- 1 | ; https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading 2 | opcache.preload_user = root 3 | opcache.preload = /app/config/preload.php 4 | ; https://symfony.com/doc/current/performance.html#don-t-check-php-files-timestamps 5 | opcache.validate_timestamps = 0 6 | -------------------------------------------------------------------------------- /frankenphp/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then 5 | # Install the project the first time PHP is started 6 | # After the installation, the following block can be deleted 7 | if [ ! -f composer.json ]; then 8 | rm -Rf tmp/ 9 | composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install 10 | 11 | cd tmp 12 | cp -Rp . .. 13 | cd - 14 | rm -Rf tmp/ 15 | 16 | composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony 17 | composer config --json extra.symfony.docker 'true' 18 | 19 | if grep -q ^DATABASE_URL= .env; then 20 | echo 'To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build --wait' 21 | sleep infinity 22 | fi 23 | fi 24 | 25 | if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then 26 | composer install --prefer-dist --no-progress --no-interaction 27 | fi 28 | 29 | # Display information about the current project 30 | # Or about an error in project initialization 31 | php bin/console -V 32 | 33 | if grep -q ^DATABASE_URL= .env; then 34 | echo 'Waiting for database to be ready...' 35 | ATTEMPTS_LEFT_TO_REACH_DATABASE=60 36 | until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do 37 | if [ $? -eq 255 ]; then 38 | # If the Doctrine command exits with 255, an unrecoverable error occurred 39 | ATTEMPTS_LEFT_TO_REACH_DATABASE=0 40 | break 41 | fi 42 | sleep 1 43 | ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1)) 44 | echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left." 45 | done 46 | 47 | if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then 48 | echo 'The database is not up or not reachable:' 49 | echo "$DATABASE_ERROR" 50 | exit 1 51 | else 52 | echo 'The database is now ready and reachable' 53 | fi 54 | 55 | if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then 56 | php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing 57 | fi 58 | fi 59 | 60 | setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var 61 | setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var 62 | 63 | echo 'PHP app ready!' 64 | fi 65 | 66 | exec docker-php-entrypoint "$@" 67 | --------------------------------------------------------------------------------