This commit is contained in:
AlexBa16
2026-06-11 21:44:19 +02:00
commit d36bce3e86
57 changed files with 8164 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
**/*.log
**/*.md
**/*.php~
**/*.dist.php
**/*.dist
**/*.cache
**/._*
**/.dockerignore
**/.DS_Store
**/.git/
**/.gitattributes
**/.gitignore
**/.gitmodules
**/compose.*.yaml
**/compose.*.yml
**/compose.yaml
**/compose.yml
**/docker-compose.*.yaml
**/docker-compose.*.yml
**/docker-compose.yaml
**/docker-compose.yml
**/Dockerfile
**/Thumbs.db
.github/
docs/
public/bundles/
tests/
var/
vendor/
.editorconfig
.env.*.local
.env.local
.env.local.php
.env.test
+22
View File
@@ -0,0 +1,22 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
charset = utf-8
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 4
[**/{Dockerfile,Caddyfile,*.sh}]
indent_style = tab
[{compose.*yaml,.devcontainer/compose.*yaml,.github/**/*.yaml}]
indent_size = 2
[**.md]
indent_size = unset
indent_style = unset
+27
View File
@@ -0,0 +1,27 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=
APP_SHARE_DIR=var/share
###< symfony/framework-bundle ###
###> symfony/routing ###
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
DEFAULT_URI=http://localhost
###< symfony/routing ###
+4
View File
@@ -0,0 +1,4 @@
###> symfony/framework-bundle ###
APP_SECRET=721f1c073aa9c56fd3c03089e7297bde
###< symfony/framework-bundle ###
+17
View File
@@ -0,0 +1,17 @@
* text=auto eol=lf
*.conf text eol=lf
*.html text eol=lf
*.ini text eol=lf
*.js text eol=lf
*.json text eol=lf
*.md text eol=lf
*.php text eol=lf
*.sh text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
bin/console text eol=lf
composer.lock text eol=lf merge=ours
*.ico binary
*.png binary
+10
View File
@@ -0,0 +1,10 @@
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/event-manager" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/orm" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/persistence" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/clock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/clock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/config" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dependency-injection" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dotenv" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/error-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/filesystem" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/flex" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/framework-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/maker-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/password-hasher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-deepclone" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php85" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-access" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-info" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/runtime" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-core" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-csrf" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-http" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/twig-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/twig-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/type-info" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-dumper" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/yaml" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/gym-tracker.iml" filepath="$PROJECT_DIR$/.idea/gym-tracker.iml" />
</modules>
</component>
</project>
Generated
+88
View File
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-deepclone" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php85" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.5" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+178
View File
@@ -0,0 +1,178 @@
#syntax=docker/dockerfile:1
# Versions
FROM dunglas/frankenphp:1-php8.5 AS frankenphp_upstream
# The different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/build/building/multi-stage/#stop-at-a-specific-build-stage
# https://docs.docker.com/reference/compose-file/build/#target
# Base FrankenPHP image
FROM frankenphp_upstream AS frankenphp_base
SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
WORKDIR /app
# persistent deps
# hadolint ignore=DL3008
RUN <<-EOF
apt-get update
apt-get install -y --no-install-recommends \
file \
git
install-php-extensions \
@composer \
apcu \
intl \
opcache \
zip
rm -rf /var/lib/apt/lists/*
EOF
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER=1
ENV PHP_INI_SCAN_DIR=":$PHP_INI_DIR/app.conf.d"
###> recipes ###
###< recipes ###
COPY --link frankenphp/conf.d/10-app.ini $PHP_INI_DIR/app.conf.d/
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
COPY --link frankenphp/Caddyfile /etc/frankenphp/Caddyfile
ENTRYPOINT ["docker-entrypoint"]
HEALTHCHECK --start-period=60s CMD php -r 'exit(false === @file_get_contents("http://localhost:2019/metrics", context: stream_context_create(["http" => ["timeout" => 5]])) ? 1 : 0);'
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]
# Dev FrankenPHP image
FROM frankenphp_base AS frankenphp_dev
ENV APP_ENV=dev
ENV XDEBUG_MODE=off
ENV FRANKENPHP_WORKER_CONFIG=watch
# dev dependencies
# hadolint ignore=DL3008
RUN <<-EOF
mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
apt-get update
apt-get install -y --no-install-recommends \
aggregate \
curl \
dnsmasq \
dnsutils \
iproute2 \
ipset \
iptables \
jq \
sudo
install-php-extensions xdebug
rm -rf /var/lib/apt/lists/*
useradd -m -s /bin/bash nonroot
echo "nonroot ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/nonroot
git config --system --add safe.directory /app
EOF
COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ]
# Builder for the prod FrankenPHP image
FROM frankenphp_base AS frankenphp_prod_builder
ENV APP_ENV=prod
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/
# prevent the reinstallation of vendors at every changes in the source code
COPY --link composer.* symfony.* ./
RUN composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress
# copy sources
COPY --link --exclude=frankenphp/ . ./
RUN <<-EOF
mkdir -p var/cache var/log var/share
composer dump-autoload --classmap-authoritative --no-dev
composer dump-env prod
composer run-script --no-dev post-install-cmd
if [ -f importmap.php ]; then
php bin/console asset-map:compile
fi
chmod +x bin/console
chmod -R g=u var
sync
EOF
# Collect shared libraries needed by FrankenPHP and PHP extensions
# hadolint ignore=DL3008,SC3054,DL4006
RUN <<-'EOF'
apt-get update
apt-get install -y --no-install-recommends libtree
mkdir -p /tmp/libs
BINARIES=(frankenphp php file)
for target in $(printf '%s\n' "${BINARIES[@]}" | xargs -I{} which {}) \
$(find "$(php -r 'echo ini_get("extension_dir");')" -maxdepth 2 -name "*.so"); do
libtree -pv "$target" 2>/dev/null | grep -oP '(?:── )\K/\S+(?= \[)' | while IFS= read -r lib; do
[ -f "$lib" ] && cp -n "$lib" /tmp/libs/
done
done
rm -rf /var/lib/apt/lists/*
EOF
# Prod FrankenPHP image
FROM debian:13-slim AS frankenphp_prod
SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
ENV APP_ENV=prod
ENV PHP_INI_SCAN_DIR=":/usr/local/etc/php/app.conf.d"
COPY --from=frankenphp_prod_builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
COPY --from=frankenphp_prod_builder /usr/local/bin/php /usr/local/bin/php
COPY --from=frankenphp_prod_builder /usr/local/bin/docker-php-entrypoint /usr/local/bin/docker-php-entrypoint
COPY --from=frankenphp_prod_builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=frankenphp_prod_builder /tmp/libs /usr/lib
COPY --from=frankenphp_prod_builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=frankenphp_prod_builder /usr/local/etc/php/php.ini /usr/local/etc/php/php.ini
COPY --from=frankenphp_prod_builder /usr/local/etc/php/app.conf.d /usr/local/etc/php/app.conf.d
COPY --from=frankenphp_prod_builder /etc/frankenphp/Caddyfile /etc/frankenphp/Caddyfile
# CA certificates for TLS, file/libmagic for Symfony MIME type detection
COPY --from=frankenphp_prod_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=frankenphp_prod_builder /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf
COPY --from=frankenphp_prod_builder /usr/bin/file /usr/bin/file
COPY --from=frankenphp_prod_builder /usr/lib/file/magic.mgc /usr/lib/file/magic.mgc
ENV OPENSSL_CONF=/etc/ssl/openssl.cnf XDG_CONFIG_HOME=/config XDG_DATA_HOME=/data
RUN <<-EOF
mkdir -p /data/caddy /config/caddy
chown -R www-data:www-data /data /config
# Remove setuid/setgid bits
find / -perm /6000 -type f -exec chmod a-s {} + 2>/dev/null || true
EOF
COPY --link --exclude=var --from=frankenphp_prod_builder /app /app
# Group 0 + g=u for arbitrary-UID runtimes (e.g. OpenShift).
COPY --chown=www-data:0 --from=frankenphp_prod_builder /app/var /app/var
RUN chmod g=u /app/var
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
USER www-data
WORKDIR /app
ENTRYPOINT ["docker-entrypoint"]
HEALTHCHECK --start-period=60s CMD php -r 'exit(false === @file_get_contents("http://localhost:2019/metrics", context: stream_context_create(["http" => ["timeout" => 5]])) ? 1 : 0);'
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+60
View File
@@ -0,0 +1,60 @@
# Symfony Docker
A [Docker](https://www.docker.com/)-based installer and runtime for the [Symfony](https://symfony.com) web framework,
with [FrankenPHP](https://frankenphp.dev) and [Caddy](https://caddyserver.com/) inside!
Specially tailored for coding agents: ships with a [Dev Container](https://containers.dev/) configuration
that lets [Claude Code](https://claude.ai/claude-code) (and other AI coding assistants) run in fully autonomous
mode inside a sandboxed environment.
![CI](https://github.com/dunglas/symfony-docker/workflows/CI/badge.svg)
## Getting Started
1. If not already done, [install Docker Compose](https://docs.docker.com/compose/install/) (v2.10+)
2. Run `docker compose build --pull --no-cache` to build fresh images
3. Run `docker compose up --wait` to set up and start a fresh Symfony project
4. Open `https://localhost` in your favorite web browser and [accept the auto-generated TLS certificate](https://stackoverflow.com/a/15076602/1352334)
5. Run `docker compose down --remove-orphans` to stop the Docker containers.
## Features
- Production, development and CI ready
- Just 1 service by default
- Super-readable configuration
- Blazing-fast performance thanks to [the worker mode of FrankenPHP](https://frankenphp.dev/docs/worker/)
- [Installation of extra Docker Compose services](docs/extra-services.md) with Symfony Flex
- Automatic HTTPS (in dev and prod)
- HTTP/3 and [Early Hints](https://symfony.com/blog/new-in-symfony-6-3-early-hints) support
- Real-time messaging thanks to a built-in [Mercure hub](https://symfony.com/doc/current/mercure.html)
- [Vulcain](https://vulcain.rocks) support
- Native [XDebug](docs/xdebug.md) integration
- [Hot Reloading](https://frankenphp.dev/docs/hot-reload/)
- [Dev Container](https://containers.dev/) support, optimized for AI coding agents
- [AI coding agents](docs/agents.md) with sandboxing out of the box
- Rootless, slim production image
**Enjoy!**
## Docs
1. [Options available](docs/options.md)
2. [Using Symfony Docker with an existing project](docs/existing-project.md)
3. [Support for extra services](docs/extra-services.md)
4. [Deploying in production](docs/production.md)
5. [Debugging with Xdebug](docs/xdebug.md)
6. [TLS Certificates](docs/tls.md)
7. [Using MySQL instead of PostgreSQL](docs/mysql.md)
8. [Using Alpine Linux instead of Debian](docs/alpine.md)
9. [Using a Makefile](docs/makefile.md)
10. [Updating the template](docs/updating.md)
11. [Troubleshooting](docs/troubleshooting.md)
12. [Using AI Coding Agents](docs/agents.md)
## License
Symfony Docker is available under the MIT License.
## Credits
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).
Executable
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_dir(dirname(__DIR__).'/vendor')) {
throw new LogicException('Dependencies are missing. Try running "composer install".');
}
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
return new Application($kernel);
};
+30
View File
@@ -0,0 +1,30 @@
---
# Development environment override
services:
php:
image: ${IMAGES_PREFIX:-}app-php-dev
build:
context: .
target: frankenphp_dev
volumes:
- ./:/app
- ./frankenphp/Caddyfile:/etc/frankenphp/Caddyfile:ro
- ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro
# Keep var/ off the bind-mount for faster I/O on Mac/Windows; comment to inspect from the host.
- /app/var
# If you develop on Mac or Windows you can remove the vendor/ directory
# from the bind-mount for better performance by enabling the next line:
#- /app/vendor
environment:
FRANKENPHP_WORKER_CONFIG: watch
FRANKENPHP_SITE_CONFIG: hot_reload
MERCURE_EXTRA_DIRECTIVES: demo
# See https://xdebug.org/docs/all_settings#mode
XDEBUG_MODE: "${XDEBUG_MODE:-develop}"
APP_ENV: "${APP_ENV:-dev}"
extra_hosts:
# Ensure that host.docker.internal is correctly defined on Linux
- host.docker.internal:host-gateway
tty: true
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###
+12
View File
@@ -0,0 +1,12 @@
---
# Production environment override
services:
php:
image: ${IMAGES_PREFIX:-}app-php-prod
build:
context: .
target: frankenphp_prod
environment:
APP_SECRET: ${APP_SECRET}
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
+41
View File
@@ -0,0 +1,41 @@
---
services:
php:
restart: unless-stopped
environment:
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
DEFAULT_URI: https://${SERVER_NAME:-localhost}:${HTTPS_PORT:-443}
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
# Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8}
# Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}:${HTTPS_PORT:-443}/.well-known/mercure}
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
volumes:
- caddy_data:/data
- caddy_config:/config
ports:
- name: http
target: 80
published: ${HTTP_PORT:-80}
protocol: tcp
- name: https
target: 443
published: ${HTTPS_PORT:-443}
protocol: tcp
- name: http3
target: 443
published: ${HTTP3_PORT:-443}
protocol: udp
# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###
volumes:
caddy_data:
caddy_config:
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###
+78
View File
@@ -0,0 +1,78 @@
{
"name": "symfony/skeleton",
"type": "project",
"license": "MIT",
"description": "A minimal Symfony project recommended to create bare bones applications",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.5.7",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/orm": "^3.6",
"symfony/console": "8.1.*",
"symfony/dotenv": "8.1.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "8.1.*",
"symfony/runtime": "8.1.*",
"symfony/security-bundle": "8.1.*",
"symfony/twig-bundle": "8.1.*",
"symfony/yaml": "8.1.*"
},
"config": {
"allow-plugins": {
"php-http/discovery": true,
"symfony/flex": true,
"symfony/runtime": true
},
"bump-after-update": true,
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*",
"symfony/polyfill-php82": "*",
"symfony/polyfill-php83": "*",
"symfony/polyfill-php84": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "8.1.*",
"docker": true
}
},
"require-dev": {
"symfony/maker-bundle": "^1.67"
}
}
Generated
+4723
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
];
+19
View File
@@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null
+15
View File
@@ -0,0 +1,15 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
# Note that the session will be started ONLY if you read or write from it.
session: true
#esi: true
#fragments: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file
+3
View File
@@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true
+10
View File
@@ -0,0 +1,10 @@
framework:
router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
default_uri: '%env(DEFAULT_URI)%'
when@prod:
framework:
router:
strict_requirements: null
+39
View File
@@ -0,0 +1,39 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
# Ensure dev tools and static assets are always allowed
pattern: ^/(_profiler|_wdt|assets|build)/
security: false
main:
lazy: true
provider: users_in_memory
# Activate different ways to authenticate:
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Note: Only the *first* matching rule is applied
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# Password hashers are resource-intensive by design to ensure security.
# In tests, it's safe to reduce their cost to improve performance.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
+6
View File
@@ -0,0 +1,6 @@
twig:
file_name_pattern: '*.twig'
when@test:
twig:
strict_variables: true
+5
View File
@@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}
+1176
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
# This file is the entry point to configure the routes of your app.
# Methods with the #[Route] attribute are automatically imported.
# See also https://symfony.com/doc/current/routing.html
# To list all registered routes, run the following command:
# bin/console debug:router
controllers:
resource: routing.controllers
+4
View File
@@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
prefix: /_error
+3
View File
@@ -0,0 +1,3 @@
_security_logout:
resource: security.route_loader.logout
type: service
+23
View File
@@ -0,0 +1,23 @@
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# See also https://symfony.com/doc/current/service_container/import.html
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
+162
View File
@@ -0,0 +1,162 @@
# Using AI Coding Agents with Dev Containers
This project ships with a [Dev Container](https://containers.dev/) configuration that enables
AI coding agents to run autonomously inside a sandboxed environment with network-level restrictions.
[Claude Code](https://claude.ai/claude-code) is pre-installed and configured out of the box,
but the setup also works with other agents such as [OpenAI Codex CLI](https://github.com/openai/codex)
and [opencode](https://opencode.ai).
This setup is ideal for letting AI agents work on your Symfony project autonomously
while ensuring they cannot reach arbitrary internet hosts.
## Prerequisites
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) (or any Docker-compatible runtime)
- [Visual Studio Code](https://code.visualstudio.com/) with the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension
- A valid subscription or API key for the agent you want to use
## Quick Start
1. Open the project in Visual Studio Code.
2. When prompted "Reopen in Container", click **Reopen in Container**.
Alternatively, open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and run
**Dev Containers: Reopen in Container**.
3. Wait for the container to build and start. On each container start, the
`postStartCommand` configures the firewall automatically.
4. Claude Code is pre-installed and configured in YOLO mode — open the Claude Code
panel in Visual Studio Code or run `claude` in the integrated terminal to start using it.
That's it. Claude Code will run without permission prompts, and the firewall ensures
network access is restricted to only the necessary services.
## What Is YOLO Mode?
YOLO mode (also known as "bypass permissions" mode) allows Claude Code to execute
commands, edit files, and perform actions without asking for confirmation at each step.
This dramatically speeds up autonomous coding workflows.
The Dev Container configuration enables this via two Visual Studio Code settings:
```json
{
"claudeCode.allowDangerouslySkipPermissions": true,
"claudeCode.initialPermissionMode": "bypassPermissions"
}
```
## Network Sandboxing
Running an AI agent with full autonomy requires guardrails. The Dev Container includes
a firewall script (`.devcontainer/init-firewall.sh`) that locks down outbound network
access using `iptables` and `ipset`. Only the following destinations are allowed:
| Destination | Reason |
| ------------------------------------------------- | ------------------------------- |
| GitHub (`github.com`, `api.github.com`) | Git operations, API access |
| Anthropic (`anthropic.com`) | Claude Code backend |
| npm registry (`registry.npmjs.org`) | Node.js dependencies |
| Packagist (`packagist.org`, `repo.packagist.org`) | PHP/Composer dependencies |
| Visual Studio Code Marketplace | Extension downloads |
| Sentry, Statsig | Telemetry (used by Claude Code) |
| Host gateway IP | Communication with Docker host |
All other outbound connections are **rejected**. The firewall uses
[dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) to dynamically resolve
and whitelist IPs for allowed domains, handling CDN IP rotation gracefully.
Inbound connections from the host gateway IP are allowed on all ports,
and ports 80, 443 (TCP), and 443 (UDP/HTTP3) are open to any source
so you can access your Symfony app from the host browser.
## Customizing the Allowed Domains
To allow additional domains (e.g., a private registry or API), edit
`.devcontainer/init-firewall.sh` and add them to the `ipset` line in the
dnsmasq configuration section:
```bash
# Domains are '/'-separated, ending with the ipset name
ipset=/github.com/anthropic.com/your-domain.com/allowed-domains
```
Then rebuild the Dev Container for the changes to take effect.
## Using Other Agents
The Dev Container's network sandbox and project context (`.devcontainer/AGENTS.md`) work
with any AI coding agent. You just need to install the agent and whitelist the domains it
needs to reach.
### OpenAI Codex CLI
1. Add the OpenAI API domain to the firewall allowlist in `.devcontainer/init-firewall.sh`
(see [Customizing the Allowed Domains](#customizing-the-allowed-domains)):
```bash
ipset=/.../api.openai.com/allowed-domains
```
2. Install and run Codex inside the container:
```console
npm install -g @openai/codex
export OPENAI_API_KEY=your-key
codex --full-auto
```
### opencode
1. Add the required API domain to the firewall allowlist (e.g., `api.anthropic.com`,
`api.openai.com`, or your provider's domain).
2. Install and run opencode inside the container:
```console
curl -fsSL https://opencode.ai/install | bash
opencode
```
### Other Agents
For any other agent, follow the same pattern:
1. Add the agent's API domain(s) to the firewall allowlist.
2. Install the agent inside the container.
3. Run it — the `.devcontainer/AGENTS.md` file provides project context
to agents that support the convention.
## Using Without Visual Studio Code
The Dev Container configuration works with any tool that supports the
[Dev Container specification](https://containers.dev/), including:
- [Dev Container CLI](https://github.com/devcontainers/cli) (`devcontainer up`)
- [GitHub Codespaces](https://github.com/features/codespaces)
- JetBrains IDEs (with the Dev Containers plugin)
To use Claude Code from the terminal inside the container:
```console
claude
```
To start directly in YOLO mode from the CLI:
```console
claude --dangerously-skip-permissions
```
## Troubleshooting
### Firewall blocks a required domain
If your agent or Composer/npm fails to reach a service, check the firewall
logs and add the domain to the dnsmasq allowlist as described above.
### Container fails to start
Ensure Docker is running and that you have allocated enough resources
(at least 2 GB of RAM for the container). The firewall setup requires
`NET_ADMIN` capability, which the Dev Container configures automatically
via Docker Compose.
+61
View File
@@ -0,0 +1,61 @@
# Using Alpine Linux Instead of Debian
By default, Symfony Docker uses Debian-based FrankenPHP Docker images.
This is the recommended solution.
Alternatively, it's possible to use Alpine-based images, which are smaller but
are known to be slower, and have several known issues.
To switch to Alpine-based images, apply the following changes to the `Dockerfile`:
<!-- markdownlint-disable MD010 -->
```diff
-FROM dunglas/frankenphp:1-php8.5 AS frankenphp_upstream
+FROM dunglas/frankenphp:1-php8.5-alpine AS frankenphp_upstream
-SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
+SHELL ["/bin/ash", "-euxo", "-c"]
-# hadolint ignore=DL3008
-RUN <<-EOF
- apt-get update
- apt-get install -y --no-install-recommends \
- file \
- git
+# hadolint ignore=DL3018
+RUN <<-EOF
+ apk add --no-cache \
+ file \
+ git
install-php-extensions \
-# hadolint ignore=DL3008,SC3054,DL4006
-RUN <<-'EOF'
- apt-get update
- apt-get install -y --no-install-recommends libtree
+# hadolint ignore=DL3018,SC3054,DL4006
+RUN <<-'EOF'
+ apk add --no-cache libtree
mkdir -p /tmp/libs
- BINARIES=(frankenphp php file)
- for target in $(printf '%s\n' "${BINARIES[@]}" | xargs -I{} which {}) \
+ BINARIES="frankenphp php file"
+ for target in $(printf '%s\n' $BINARIES | xargs -I{} which {}) \
- libtree -pv "$target" 2>/dev/null | grep -oP '(?:── )\K/\S+(?= \[)' | while IFS= read -r lib; do
+ libtree -pv "$target" 2>/dev/null | sed -n 's/.*── \(\/[^ ]*\) \[.*/\1/p' | while IFS= read -r lib; do
- rm -rf /var/lib/apt/lists/*
-FROM debian:13-slim AS frankenphp_prod
+FROM alpine:3 AS frankenphp_prod
-SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
+SHELL ["/bin/ash", "-euxo", "-c"]
-COPY --from=frankenphp_prod_builder /usr/lib/file/magic.mgc /usr/lib/file/magic.mgc
+COPY --from=frankenphp_prod_builder /usr/share/misc/magic.mgc /usr/share/misc/magic.mgc
```
<!-- markdownlint-enable MD010 -->
Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

+81
View File
@@ -0,0 +1,81 @@
# Installing on an Existing Project
It's also possible to use Symfony Docker with existing projects!
First, [download this skeleton](https://github.com/dunglas/symfony-docker).
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.
You can copy the contents of the repository using Git and tar.
This will not contain `.git` or any uncommitted changes.
```console
git archive --format=tar HEAD | tar -xC my-existing-project/
```
If you downloaded the skeleton as a ZIP you can just copy the extracted files:
```console
cp -Rp symfony-docker/. my-existing-project/
```
Enable the Docker support of Symfony Flex:
```console
composer config --json extra.symfony.docker 'true'
```
The [worker mode of FrankenPHP](https://frankenphp.dev/docs/worker/) is enabled by default.
To use it with Symfony ≤ 7.3, install the FrankenPHP runtime:
```console
composer require runtime/frankenphp-symfony
```
Then update worker configuration:
<!-- markdownlint-disable MD010 -->
```diff
worker {
file ./public/index.php
+ env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
{$FRANKENPHP_WORKER_CONFIG}
}
```
<!-- markdownlint-enable MD010 -->
> [!TIP]
>
> You can disable worker mode by removing the `worker` directive from the `frankenphp`
> global option in your `Caddyfile`.
Re-execute the recipes to update the Docker-related files according to
the packages you use:
```console
rm symfony.lock
composer recipes:install --force --verbose
```
Double-check the changes, revert the changes that you don't want to keep:
```console
git diff
```
Build the Docker images:
```console
docker compose build --pull --no-cache
```
Start the project!
```console
docker compose up --wait
```
Browse `https://localhost`, your Docker configuration is ready!
+25
View File
@@ -0,0 +1,25 @@
# Support for Extra Services
Symfony Docker is extensible.
When you install a compatible Composer package using Symfony Flex,
the recipe will automatically modify the `Dockerfile` and `compose.yaml` files
to fulfill the requirements of this package.
The currently supported packages are:
- `symfony/orm-pack`: install a PostgreSQL service
- `symfony/mercure-bundle`: use the Mercure.rocks module shipped with Caddy
- `symfony/panther`: install Chromium and its drivers
- `symfony/mailer`: install a Mailpit service
- `blackfireio/blackfire-symfony-meta`: install a Blackfire service
> [!NOTE]
>
> If a recipe modifies the Dockerfile, the container needs to be rebuilt.
<!-- -->
> [!WARNING]
>
> We recommend that you use the `composer require` command inside the container
> in development mode so that recipes can be applied correctly.
+129
View File
@@ -0,0 +1,129 @@
# Makefile
Here is a Makefile template. It provides some shortcuts for the most common tasks.
To use it, create a new `Makefile` file at the root of your project. Copy/paste
the content in the template section. To view all the available commands, run `make`.
For example, in the [getting started section](/README.md#getting-started), the
`docker compose` commands could be replaced by:
1. Run `make build` to build fresh images
2. Run `make up` (detached mode without logs)
3. Run `make down` to stop the Docker containers
Of course, this template is basic for now. But, as your application is growing,
you will probably want to add some targets like running your tests as described
in [the Symfony book](https://symfony.com/doc/current/the-fast-track/en/17-tests.html#automating-your-workflow-with-a-makefile).
You can also find a more complete example in this [snippet](https://www.strangebuzz.com/en/snippets/the-perfect-makefile-for-symfony).
If you want to run make from within the `php` container, in the [Dockerfile](/Dockerfile),
add:
<!-- markdownlint-disable MD010 -->
```diff
gettext \
git \
+ make \
```
<!-- markdownlint-enable MD010 -->
And rebuild the PHP image.
> [!NOTE]
>
> 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.
## The Template
<!-- markdownlint-disable MD010 MD013 -->
```Makefile
# Executables (local)
DOCKER_COMP = docker compose
# Docker containers
PHP_CONT = $(DOCKER_COMP) exec php
# Executables
PHP = $(PHP_CONT) php
COMPOSER = $(PHP_CONT) composer
SYMFONY = $(PHP) bin/console
# Misc
.DEFAULT_GOAL = help
.PHONY : help build up start down logs sh composer vendor sf cc test
## —— 🎵 🐳 The Symfony Docker Makefile 🐳 🎵 ——————————————————————————————————
help: ## Outputs this help screen
@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/'
## —— Docker 🐳 ————————————————————————————————————————————————————————————————
build: ## Builds the Docker images
@$(DOCKER_COMP) build --pull --no-cache
up: ## Start the docker hub in detached mode (no logs)
@$(DOCKER_COMP) up --detach
start: build up ## Build and start the containers
down: ## Stop the docker hub
@$(DOCKER_COMP) down --remove-orphans
logs: ## Show live logs
@$(DOCKER_COMP) logs --tail=0 --follow
sh: ## Connect to the FrankenPHP container
@$(PHP_CONT) sh
bash: ## Connect to the FrankenPHP container via bash so up and down arrows go to previous commands
@$(PHP_CONT) bash
test: ## Start tests with phpunit, pass the parameter "c=" to add options to phpunit, example: make test c="--group e2e --stop-on-failure"
@$(eval c ?=)
@$(DOCKER_COMP) exec -e APP_ENV=test php bin/phpunit $(c)
## —— Composer 🧙 ——————————————————————————————————————————————————————————————
composer: ## Run composer, pass the parameter "c=" to run a given command, example: make composer c='req symfony/orm-pack'
@$(eval c ?=)
@$(COMPOSER) $(c)
vendor: ## Install vendors according to the current composer.lock file
vendor: c=install --prefer-dist --no-dev --no-progress --no-scripts --no-interaction
vendor: composer
## —— Symfony 🎵 ———————————————————————————————————————————————————————————————
sf: ## List all Symfony commands or pass the parameter "c=" to run a given command, example: make sf c=about
@$(eval c ?=)
@$(SYMFONY) $(c)
cc: c=c:c ## Clear the cache
cc: sf
```
<!-- markdownlint-enable MD010 MD013 -->
## Adding and Modifying Jobs
Make sure to add this configuration to the [.editorconfig](/.editorconfig) file,
so that it forces your editor to use tabs instead of spaces.
> [!NOTE]
>
> Makefiles are not compatible with spaces by default.
```.editorconfig
[Makefile]
indent_style = tab
```
If you still want to use space, you can configure the prefix in the Makefile itself.
See [this answer on Stack Exchange](https://retrocomputing.stackexchange.com/a/20303).
+107
View File
@@ -0,0 +1,107 @@
# Using MySQL
The Docker configuration of this repository is extensible thanks to Flex recipes.
By default, the recipe installs PostgreSQL.
If you prefer to work with MySQL, follow these steps:
First, install the `symfony/orm-pack` package as described:
```console
docker compose exec php composer req symfony/orm-pack
```
## Docker Configuration
Change the database image to use MySQL instead of PostgreSQL in `compose.yaml`:
<!-- markdownlint-disable MD013 -->
```diff
###> doctrine/doctrine-bundle ###
- image: postgres:${POSTGRES_VERSION:-16}-alpine
+ image: mysql:${MYSQL_VERSION:-8.0.32}
environment:
- POSTGRES_DB: ${POSTGRES_DB:-app}
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
# You should definitely change the password in production
+ MYSQL_RANDOM_ROOT_PASSWORD: "true"
- POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-!ChangeMe!}
- POSTGRES_USER: ${POSTGRES_USER:-app}
+ MYSQL_USER: ${MYSQL_USER:-app}
healthcheck:
- test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
+ test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
timeout: 5s
retries: 5
start_period: 60s
volumes:
- - database_data:/var/lib/postgresql/data:rw
+ - database_data:/var/lib/mysql:rw
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
- # - ./docker/db/data:/var/lib/postgresql/data:rw
+ # - ./docker/db/data:/var/lib/mysql:rw
###< doctrine/doctrine-bundle ###
```
<!-- markdownlint-enable MD013 -->
Depending on the database configuration,
modify the environment in the same file at `services.php.environment.DATABASE_URL`:
```yaml
DATABASE_URL: mysql://${MYSQL_USER:-app}:${MYSQL_PASSWORD:-!ChangeMe!}@database:3306/${MYSQL_DATABASE:-app}?serverVersion=${MYSQL_VERSION:-8.0.32}&charset=${MYSQL_CHARSET:-utf8mb4}
```
Since we changed the port, we also have to define this in the `compose.override.yaml`:
```diff
###> doctrine/doctrine-bundle ###
database:
ports:
- - "5432"
+ - "3306"
###< doctrine/doctrine-bundle ###
```
Last but not least, we need to install the MySQL driver in `Dockerfile`:
```diff
###> doctrine/doctrine-bundle ###
-RUN install-php-extensions pdo_pgsql
+RUN install-php-extensions pdo_mysql
###< doctrine/doctrine-bundle ###
```
## Change Environment
Change the database configuration in `.env`:
```dotenv
DATABASE_URL=mysql://${MYSQL_USER:-app}:${MYSQL_PASSWORD:-!ChangeMe!}@database:3306/${MYSQL_DATABASE:-app}?serverVersion=${MYSQL_VERSION:-8.0.32}&charset=${MYSQL_CHARSET:-utf8mb4}
```
## Final steps
Rebuild the Docker environment:
```console
docker compose down --remove-orphans && docker compose build --pull --no-cache
```
Start the services:
```console
docker compose up --wait
```
Test your setup:
<!-- markdownlint-disable MD013 -->
```console
docker compose exec php bin/console dbal:run-sql -q "SELECT 1" && echo "OK" || echo "Connection is not working"
```
<!-- markdownlint-enable MD013 -->
+121
View File
@@ -0,0 +1,121 @@
# Docker Build Options
You can customize the Docker build process using these environment variables.
> [!NOTE]
>
> All Symfony-specific environment variables are used only if no `composer.json`
> file is found in the project directory.
## Selecting a Specific Symfony Version
Use the `SYMFONY_VERSION` environment variable to select a specific Symfony version.
For instance, use the following command to install Symfony 6.4:
On Linux:
```console
SYMFONY_VERSION=6.4.* docker compose up --wait
```
On Windows:
```console
set SYMFONY_VERSION=6.4.* && docker compose up --wait&set SYMFONY_VERSION=
```
<!-- markdownlint-disable MD010 -->
> [!NOTE]
>
> If you're using Symfony 7.3 or earlier with FrankenPHP in worker mode, you also need to follow these steps
>
> ```console
> composer require runtime/frankenphp-symfony
> ```
>
> Add `env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime` in the `frankenphp/Caddyfile` in the `worker` section.
>
> ```diff
> worker {
> file ./public/index.php
> + env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
> {$FRANKENPHP_WORKER_CONFIG}
> }
> ```
<!-- markdownlint-enable MD010 -->
## Installing Development Versions of Symfony
To install a non-stable version of Symfony,
use the `STABILITY` environment variable during the build.
The value must be [a valid Composer stability option](https://getcomposer.org/doc/04-schema.md#minimum-stability).
For instance, use the following command to use the development branch of Symfony:
On Linux:
```console
STABILITY=dev docker compose up --wait
```
On Windows:
```console
set STABILITY=dev && docker compose up --wait&set STABILITY=
```
## Using Custom HTTP Ports
Use the environment variables `HTTP_PORT`, `HTTPS_PORT` and/or `HTTP3_PORT`
to adjust the ports to your needs, e.g.
```console
HTTP_PORT=8000 HTTPS_PORT=4443 HTTP3_PORT=4443 docker compose up --wait
```
to access your application on [https://localhost:4443](https://localhost:4443).
> [!NOTE]
>
> 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.
## `Caddyfile` Options
You can also customize the `Caddyfile` by using the following environment variables
to inject options block, directive or configuration.
> [!TIP]
>
> 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.
<!-- markdownlint-disable MD013 -->
| Environment variable | Description | Default value |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `CADDY_GLOBAL_OPTIONS` | the [global options block](https://caddyserver.com/docs/caddyfile/options#global-options), one per line | |
| `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 | |
| `CADDY_SERVER_EXTRA_DIRECTIVES` | the [`Caddyfile` directives](https://caddyserver.com/docs/caddyfile/concepts#directives) | |
| `CADDY_SERVER_LOG_OPTIONS` | the [server log options block](https://caddyserver.com/docs/caddyfile/directives/log), one per line | |
| `SERVER_NAME` | the server name or address | `localhost` |
| `FRANKENPHP_CONFIG` | a list of extra [FrankenPHP global directives](https://frankenphp.dev/docs/config/#caddyfile-config), one per line | |
| `FRANKENPHP_WORKER_CONFIG` | a list of extra [FrankenPHP worker directives](https://frankenphp.dev/docs/config/#caddyfile-config), one per line | |
| `MERCURE_PUBLISHER_JWT_KEY` | the JWT key to use for publishers | |
| `MERCURE_PUBLISHER_JWT_ALG` | the JWT algorithm to use for publishers | `HS256` |
| `MERCURE_SUBSCRIBER_JWT_KEY` | the JWT key to use for subscribers | |
| `MERCURE_SUBSCRIBER_JWT_ALG` | the JWT algorithm to use for subscribers | `HS256` |
| `MERCURE_EXTRA_DIRECTIVES` | a list of extra [Mercure directives](https://mercure.rocks/docs/hub/config), one per line | |
<!-- markdownlint-enable MD013 -->
### Customizing the Server Name
```console
SERVER_NAME="app.localhost" docker compose up --wait
```
+140
View File
@@ -0,0 +1,140 @@
# Deploying in Production
Symfony Docker provides Docker images and a Docker Compose definition optimized
for production usage.
In this tutorial, we will learn how to deploy our Symfony application
on a single server using Docker Compose.
## Preparing a Server
To deploy your application in production, you need a server.
In this tutorial, we will use a virtual machine provided by DigitalOcean,
but any Linux server can work.
If you already have a Linux server with Docker Compose installed,
you can skip straight to [the next section](#configuring-a-domain-name).
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".
Then, click on the "Marketplace" tab under the "Choose an image" section
and search for the app named "Docker".
This will provision an Ubuntu server with the latest versions of Docker and
Docker Compose already installed!
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.
![Deploying a Symfony app on DigitalOcean with Docker Compose](digitalocean-droplet.png)
You can keep the defaults for other settings, or tweak them according to your needs.
Don't forget to add your SSH key or create a password
then press the "Finalize and create" button.
Then, wait a few seconds while your Droplet is provisioning.
When your Droplet is ready, use SSH to connect:
```console
ssh root@<droplet-ip>
```
## Configuring a Domain Name
In most cases, you'll want to associate a domain name with your site.
If you don't own a domain name yet, you'll have to buy one through a registrar.
Then create a DNS record of type `A` for your domain name pointing
to the IP address of your server:
```dns
your-domain-name.example.com. IN A 207.154.233.113
```
Example with the DigitalOcean Domains service ("Networking" > "Domains"):
![Configuring DNS on DigitalOcean](digitalocean-dns.png)
> [!NOTE]
>
> 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.
## Deploying
Copy your project on the server using `git clone`, `scp`, or any other tool
that may fit your need.
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).
Deploy keys are also [supported by GitLab](https://docs.gitlab.com/user/project/deploy_keys/).
Example with Git:
```console
git clone git@github.com:<username>/<project-name>.git
```
Go into the directory containing your project (`<project-name>`),
and start the app in production mode:
```console
# Build fresh production image
docker compose -f compose.yaml -f compose.prod.yaml build --pull --no-cache
# Start container
SERVER_NAME=your-domain-name.example.com \
APP_SECRET=ChangeMe \
CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \
docker compose -f compose.yaml -f compose.prod.yaml up --wait
```
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.
Your server is up and running, and a HTTPS certificate has been automatically
generated for you.
Go to `https://your-domain-name.example.com` and enjoy!
> [!CAUTION]
>
> 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 issues.
## Disabling HTTPS
Alternatively, if you don't want to expose an HTTPS server but only an HTTP one,
run the following command:
```console
SERVER_NAME=:80 \
APP_SECRET=ChangeMe \
CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \
docker compose -f compose.yaml -f compose.prod.yaml up --wait
```
## Deploying on Multiple Nodes
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/),
which is compatible with the provided Compose files.
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.
## Passing local environment variables to containers
By default, `.env.local` and `.env.*.local` files are excluded from production images.
If you want to pass them to your containers, you can use the [`env_file` attribute](https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/#use-the-env_file-attribute):
```yaml
# compose.prod.yaml
services:
php:
env_file:
- .env.prod.local
# ...
```
+88
View File
@@ -0,0 +1,88 @@
# TLS Certificates
## Trusting the Authority
With a standard installation, the authority used to sign certificates
generated in the Caddy container is not trusted by your local machine.
You must add the authority to the trust store of the host.
<!-- markdownlint-disable MD013 -->
### Linux
```console
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
```
### Mac
```console
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
```
### Windows
```console
docker compose cp php:/data/caddy/pki/authorities/local/root.crt %TEMP%/root.crt && certutil -addstore -f "ROOT" %TEMP%/root.crt
```
<!-- markdownlint-enable MD013 -->
## Using Custom TLS Certificates
By default, Caddy will automatically generate TLS certificates using Let's Encrypt
or ZeroSSL.
But sometimes you may prefer using custom certificates.
For instance, to use self-signed certificates created with [mkcert](https://github.com/FiloSottile/mkcert)
do as follows:
1. Locally install `mkcert`
2. Create the folder storing the certs:
```console
mkdir -p frankenphp/certs
```
3. Generate the certificates for your local host (example: "server-name.localhost"):
<!-- markdownlint-disable MD013 -->
```console
mkcert -cert-file frankenphp/certs/tls.pem -key-file frankenphp/certs/tls.key "server-name.localhost"
```
<!-- markdownlint-enable MD013 -->
4. Add these lines to the `./compose.override.yaml` file about `CADDY_SERVER_EXTRA_DIRECTIVES`
environment and volume for the `php` service:
```diff
php:
environment:
+ CADDY_EXTRA_CONFIG: |
+ https:// {
+ tls /etc/caddy/certs/tls.pem /etc/caddy/certs/tls.key
+ }
# ...
volumes:
+ - ./frankenphp/certs:/etc/caddy/certs:ro
# ...
```
5. Restart your `php` service
## Disabling HTTPS for Local Development
To disable HTTPS, configure your environment to use HTTP by setting the following
variables and starting the project with this command:
```console
SERVER_NAME=http://localhost \
MERCURE_PUBLIC_URL=http://localhost/.well-known/mercure \
docker compose up --wait
```
Ensure your application is accessible over HTTP by visiting `http://localhost`
in your web browser.
+53
View File
@@ -0,0 +1,53 @@
# Troubleshooting
## Editing Permissions on Linux
If you work on Linux and cannot edit some of the project files right after
the first installation, you can run the following command
to set yourself as owner of the project files that were created by the Docker container:
```console
docker compose run --rm php chown -R $(id -u):$(id -g) .
```
## TLS/HTTPS Issues
See the [TLS section](tls.md) for more details.
## Production Issues
### How To Properly Build Fresh Images for Production Use
Remember that, by default, if you run `docker compose up --wait`,
only the files `compose.yaml` and `compose.override.yaml` will be used.
See ["How Compose works"](https://docs.docker.com/compose/intro/compose-application-model)
and ["Merge Compose files"](https://docs.docker.com/compose/how-tos/multiple-compose-files/merge).
If you need to build images for production environment, you have to use the following
command:
```console
docker compose -f compose.yaml -f compose.prod.yaml build --pull --no-cache
```
### Building Dev and Prod Images
Dev and prod images use distinct image names (`app-php-dev` and `app-php-prod`),
so they won't conflict with each other.
To build and start the dev image:
```console
docker compose up --wait
```
To build and start the prod image:
```console
docker compose -f compose.yaml -f compose.prod.yaml build --pull --no-cache
docker compose -f compose.yaml -f compose.prod.yaml up --wait
```
> [!WARNING]
>
> The order of `-f` arguments matters.
+19
View File
@@ -0,0 +1,19 @@
# Updating Your Project
To import the changes made to the _Symfony Docker_ template into your project,
we recommend using [_template-sync_](https://github.com/coopTilleuls/template-sync):
1. Run the script to synchronize your project with the latest version of the skeleton:
<!-- markdownlint-disable MD013 -->
```console
curl -sSL https://raw.githubusercontent.com/coopTilleuls/template-sync/main/template-sync.sh | sh -s -- https://github.com/dunglas/symfony-docker
```
<!-- markdownlint-enable MD013 -->
2. Resolve conflicts, if any
3. Run `git cherry-pick --continue`
For more advanced options, refer to [the documentation of _template sync_](https://github.com/coopTilleuls/template-sync#template-sync).
+98
View File
@@ -0,0 +1,98 @@
# Using Xdebug
The default development image is shipped with [Xdebug](https://xdebug.org/),
a popular debugger and profiler for PHP.
When using [Dev Containers](https://containers.dev/), Xdebug is pre-configured and works out of the box.
Open the **Run and Debug** panel in Visual Studio Code and start the **Debug PHP** launch configuration, then set your breakpoints and load a page.
For other setups, because it has a significant performance overhead, the step-by-step debugger
is disabled by default.
It can be enabled by including `debug` in the values of the `XDEBUG_MODE` environment variable.
On Linux and Mac:
```console
XDEBUG_MODE=develop,debug docker compose up --wait
```
On Windows:
```console
set XDEBUG_MODE=develop,debug&& docker compose up --wait&set XDEBUG_MODE=
```
## Debugging with Xdebug and PhpStorm
First, [create a PHP debug remote server configuration](https://www.jetbrains.com/help/phpstorm/creating-a-php-debug-server-configuration.html):
1. In the `Settings/Preferences` dialog, go to `PHP | Servers`
2. Create a new server:
- Name: `symfony` (or whatever you want to use for the variable `PHP_IDE_CONFIG`)
- Host: `localhost` (or the one defined using the `SERVER_NAME` environment variable)
- Port: `443`
- Debugger: `Xdebug`
- Check `Use path mappings`
- Absolute path on the server: `/app`
You can now use the debugger!
1. In PhpStorm, open the `Run` menu and click on `Start Listening for PHP Debug Connections`
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)
Alternatively, you can use [the **Xdebug extension**](https://xdebug.org/docs/step_debug#browser-extensions)
for your preferred web browser.
3. On the 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
above.
Example:
```console
XDEBUG_SESSION=1 PHP_IDE_CONFIG="serverName=symfony" php bin/console ...
```
## Debugging with Xdebug and Visual Studio Code
1. Install necessary [PHP extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=DEVSENSE.phptools-vscode).
2. Add [debug configuration](https://code.visualstudio.com/docs/debugtest/debugging-configuration#_launch-configurations)
into your `.vscode\launch.json` file.
Example:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug PHP",
"type": "php",
"request": "launch",
"pathMappings": {
"/app": "${workspaceFolder}"
}
}
]
}
```
3. Use [Run and Debug](https://code.visualstudio.com/docs/debugtest/debugging#_start-a-debugging-session)
options and run `Debug PHP` to listen for upcoming connections
with [the **Xdebug extension**](https://xdebug.org/docs/step_debug#browser-extensions)
installed and active.
## Troubleshooting
Inspect the installation with the following command.
The Xdebug version should be displayed.
```console
$ docker compose exec php php --version
PHP ...
with Xdebug v3.x.x ...
```
+66
View File
@@ -0,0 +1,66 @@
{
skip_install_trust
{$CADDY_GLOBAL_OPTIONS}
frankenphp {
{$FRANKENPHP_CONFIG}
}
}
{$CADDY_EXTRA_CONFIG}
{$SERVER_NAME:localhost} {
log {
{$CADDY_SERVER_LOG_OPTIONS}
# Redact the authorization query parameter that can be set by Mercure
format filter {
request>uri query {
replace authorization REDACTED
}
}
}
root /app/public
encode zstd br gzip
mercure {
# Publisher JWT key
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
# Subscriber JWT key
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
# Allow anonymous subscribers (double-check that it's what you want)
anonymous
# Enable the subscription API (double-check that it's what you want)
subscriptions
# Extra directives
{$MERCURE_EXTRA_DIRECTIVES}
}
vulcain
{$CADDY_SERVER_EXTRA_DIRECTIVES}
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
header ?Permissions-Policy "browsing-topics=()"
@phpRoute {
not path /.well-known/mercure*
not file {path}
}
rewrite @phpRoute index.php
@frontController path index.php
php @frontController {
{$FRANKENPHP_SITE_CONFIG}
worker {
file ./public/index.php
{$FRANKENPHP_WORKER_CONFIG}
}
}
file_server {
hide *.php
}
}
+13
View File
@@ -0,0 +1,13 @@
expose_php = 0
date.timezone = UTC
apc.enable_cli = 1
session.use_strict_mode = 1
zend.detect_unicode = 0
; https://symfony.com/doc/current/performance.html
realpath_cache_size = 4096K
realpath_cache_ttl = 600
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 32531
opcache.memory_consumption = 256
opcache.enable_file_override = 1
+5
View File
@@ -0,0 +1,5 @@
; See https://docs.docker.com/desktop/features/networking/networking-how-tos/#connect-a-container-to-a-service-on-the-host
; See https://github.com/docker/for-linux/issues/264
; The `client_host` below may optionally be replaced with `discover_client_host=yes`
; Add `start_with_request=yes` to start debug session on each request
xdebug.client_host = host.docker.internal
+5
View File
@@ -0,0 +1,5 @@
; https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading
opcache.preload_user = www-data
opcache.preload = /app/config/preload.php
; https://symfony.com/doc/current/performance.html#don-t-check-php-files-timestamps
opcache.validate_timestamps = 0
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
set -e
if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then
composer install --prefer-dist --no-progress --no-interaction
fi
# Display information about the current project
# Or about an error in project initialization
php bin/console -V
if grep -q ^DATABASE_URL= .env; then
echo 'Waiting for database to be ready...'
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
if [ $? -eq 255 ]; then
# If the Doctrine command exits with 255, an unrecoverable error occurred
ATTEMPTS_LEFT_TO_REACH_DATABASE=0
break
fi
sleep 1
ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1))
echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left."
done
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
echo 'The database is not up or not reachable:'
echo "$DATABASE_ERROR"
exit 1
else
echo 'The database is now ready and reachable'
fi
if [ "$(find ./migrations -iname '*.php' -print -quit)" ]; then
php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing
fi
fi
echo 'PHP app ready!'
fi
exec docker-php-entrypoint "$@"
+9
View File
@@ -0,0 +1,9 @@
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return static function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
View File
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
/**
* @return list<string> An array of allowed values for APP_ENV
*/
private function getAllowedEnvs(): array
{
return ['prod', 'dev', 'test'];
}
}
+116
View File
@@ -0,0 +1,116 @@
{
"doctrine/deprecations": {
"version": "1.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "fdd756167454623e21f1d769c5b814b243782a67"
}
},
"symfony/console": {
"version": "8.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461"
},
"files": [
"bin/console"
]
},
"symfony/flex": {
"version": "2.11",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.4",
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
},
"files": [
".env",
".env.dev"
]
},
"symfony/framework-bundle": {
"version": "8.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "8.1",
"ref": "312027aea160796a50bf2d185503afdb5d71f570"
},
"files": [
"config/packages/cache.yaml",
"config/packages/framework.yaml",
"config/preload.php",
"config/routes/framework.yaml",
"config/services.yaml",
"public/index.php",
"src/Controller/.gitignore",
"src/Kernel.php",
".editorconfig"
]
},
"symfony/maker-bundle": {
"version": "1.67",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/property-info": {
"version": "8.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.3",
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
},
"files": [
"config/packages/property_info.yaml"
]
},
"symfony/routing": {
"version": "8.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.4",
"ref": "bc94c4fd86f393f3ab3947c18b830ea343e51ded"
},
"files": [
"config/packages/routing.yaml",
"config/routes.yaml"
]
},
"symfony/security-bundle": {
"version": "8.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.4",
"ref": "c42fee7802181cdd50f61b8622715829f5d2335c"
},
"files": [
"config/packages/security.yaml",
"config/routes/security.yaml"
]
},
"symfony/twig-bundle": {
"version": "8.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "f250159ebe99153d0c640a3e7742876fc7453f2c"
},
"files": [
"config/packages/twig.yaml",
"templates/base.html.twig"
]
}
}
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% endblock %}
{% set frankenphpHotReload = app.request.server.get('FRANKENPHP_HOT_RELOAD') %}
{% if frankenphpHotReload %}
<meta name="frankenphp-hot-reload:url" content="{{ frankenphpHotReload }}">
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
{% endif %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>