diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ee1301..bbc065d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,39 +1,74 @@ name: CI on: - push: - pull_request: + push: + branches: + - main + pull_request: ~ + workflow_dispatch: ~ + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true jobs: - lint: - name: Docker Lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Lint Dockerfile - uses: hadolint/hadolint-action@v3.1.0 - build: - name: Docker build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Pull images - run: docker compose pull - - name: Start services - run: docker compose up --build -d - - name: Wait for services - run: | - while status="$(docker inspect --format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}" "$(docker compose ps -q php)")"; do - case $status in - starting) sleep 1;; - healthy) exit 0;; - unhealthy) exit 1;; - esac - done - exit 1 - - name: Check HTTP reachability - run: curl http://localhost - - name: Check HTTPS reachability - run: curl -k https://localhost + tests: + name: Tests + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Build Docker images + uses: docker/bake-action@v3 + with: + pull: true + load: true + files: | + docker-compose.yml + docker-compose.override.yml + set: | + *.cache-from=type=gha,scope=${{github.ref}} + *.cache-from=type=gha,scope=refs/heads/main + *.cache-to=type=gha,scope=${{github.ref}},mode=max + - + name: Start services + run: docker compose up --wait --no-build + - + name: Check HTTP reachability + run: curl -v -o /dev/null http://localhost + - + name: Check HTTPS reachability + run: curl -vk -o /dev/null https://localhost + - + name: Create test database + if: false # Remove this line if Doctrine ORM is installed + run: docker compose exec -T php bin/console -e test doctrine:database:create + - + name: Run migrations + if: false # Remove this line if Doctrine Migrations is installed + run: docker compose exec -T php bin/console -e test doctrine:migrations:migrate --no-interaction + - + name: Run PHPUnit + if: false # Remove this line if PHPUnit is installed + run: docker compose exec -T php bin/phpunit + - + name: Doctrine Schema Validator + if: false # Remove this line if Doctrine ORM is installed + run: docker compose exec -T php bin/console -e test doctrine:schema:validate + lint: + name: Docker Lint + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Lint Dockerfiles + uses: hadolint/hadolint-action@v3.1.0 + with: + recursive: true diff --git a/Dockerfile b/Dockerfile index 8554a65..39718e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,22 @@ #syntax=docker/dockerfile:1.4 +# Versions +FROM php:8.2-fpm-alpine AS php_upstream +FROM mlocati/php-extension-installer:2 AS php_extension_installer_upstream +FROM composer/composer:2-bin AS composer_upstream +FROM caddy:2-alpine AS caddy_upstream + + # The different stages of this Dockerfile are meant to be built into separate images # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage # https://docs.docker.com/compose/compose-file/#target -# Build Caddy with the Mercure and Vulcain modules -# Temporary fix for https://github.com/dunglas/mercure/issues/770 -FROM caddy:2.7-builder-alpine AS app_caddy_builder -RUN xcaddy build v2.6.4 \ - --with github.com/dunglas/mercure/caddy \ - --with github.com/dunglas/vulcain/caddy - -# Prod image -FROM php:8.2-fpm-alpine AS app_php - -# Allow to use development versions of Symfony -ARG STABILITY="stable" -ENV STABILITY ${STABILITY} - -# Allow to select Symfony version -ARG SYMFONY_VERSION="" -ENV SYMFONY_VERSION ${SYMFONY_VERSION} - -ENV APP_ENV=prod +# Base PHP image +FROM php_upstream AS php_base WORKDIR /srv/app -# php extensions installer: https://github.com/mlocati/docker-php-extension-installer -COPY --from=mlocati/php-extension-installer:latest --link /usr/bin/install-php-extensions /usr/local/bin/ - # persistent / runtime deps # hadolint ignore=DL3018 RUN apk add --no-cache \ @@ -40,6 +27,9 @@ RUN apk add --no-cache \ git \ ; +# php extensions installer: https://github.com/mlocati/docker-php-extension-installer +COPY --from=php_extension_installer_upstream --link /usr/bin/install-php-extensions /usr/local/bin/ + RUN set -eux; \ install-php-extensions \ apcu \ @@ -51,9 +41,7 @@ RUN set -eux; \ ###> recipes ### ###< recipes ### -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" COPY --link docker/php/conf.d/app.ini $PHP_INI_DIR/conf.d/ -COPY --link docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/ COPY --link docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf RUN mkdir -p /var/run/php @@ -73,53 +61,62 @@ CMD ["php-fpm"] ENV COMPOSER_ALLOW_SUPERUSER=1 ENV PATH="${PATH}:/root/.composer/vendor/bin" -COPY --from=composer/composer:2-bin --link /composer /usr/bin/composer +COPY --from=composer_upstream --link /composer /usr/bin/composer -# prevent the reinstallation of vendors at every changes in the source code -COPY --link composer.* symfony.* ./ -RUN set -eux; \ - if [ -f composer.json ]; then \ - composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress; \ - composer clear-cache; \ - fi -# copy sources -COPY --link . ./ -RUN rm -Rf docker/ - -RUN set -eux; \ - mkdir -p var/cache var/log; \ - if [ -f composer.json ]; then \ - composer dump-autoload --classmap-authoritative --no-dev; \ - composer dump-env prod; \ - composer run-script --no-dev post-install-cmd; \ - chmod +x bin/console; sync; \ - fi - -# Dev image -FROM app_php AS app_php_dev +# Dev PHP image +FROM php_base AS php_dev ENV APP_ENV=dev XDEBUG_MODE=off VOLUME /srv/app/var/ -RUN rm "$PHP_INI_DIR/conf.d/app.prod.ini"; \ - mv "$PHP_INI_DIR/php.ini" "$PHP_INI_DIR/php.ini-production"; \ - mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" - -COPY --link docker/php/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/ +RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" RUN set -eux; \ install-php-extensions \ xdebug \ ; -RUN rm -f .env.local.php +COPY --link docker/php/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/ -# Caddy image -FROM caddy:2-alpine AS app_caddy +# Prod PHP image +FROM php_base AS php_prod + +ENV APP_ENV=prod + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" +COPY --link docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/ + +# prevent the reinstallation of vendors at every changes in the source code +COPY --link composer.* symfony.* ./ +RUN set -eux; \ + composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress + +# copy sources +COPY --link . ./ +RUN rm -Rf docker/ + +RUN set -eux; \ + mkdir -p var/cache var/log; \ + composer dump-autoload --classmap-authoritative --no-dev; \ + composer dump-env prod; \ + composer run-script --no-dev post-install-cmd; \ + chmod +x bin/console; sync; + + +# Base Caddy image +FROM caddy_upstream AS caddy_base + +ARG TARGETARCH WORKDIR /srv/app -COPY --from=app_caddy_builder --link /usr/bin/caddy /usr/bin/caddy -COPY --from=app_php --link /srv/app/public public/ +# Download Caddy compiled with the Mercure and Vulcain modules +ADD --chmod=500 https://caddyserver.com/api/download?os=linux&arch=$TARGETARCH&p=github.com/dunglas/mercure/caddy&p=github.com/dunglas/vulcain/caddy /usr/bin/caddy + COPY --link docker/caddy/Caddyfile /etc/caddy/Caddyfile + +# Prod Caddy image +FROM caddy_base AS caddy_prod + +COPY --from=php_prod --link /srv/app/public public/ diff --git a/README.md b/README.md index 06c5e3d..a11d803 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ A [Docker](https://www.docker.com/)-based installer and runtime for the [Symfony ## 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` (the logs will be displayed in the current shell) +2. Run `docker compose build --no-cache` to build fresh images +3. Run `docker compose up --pull --wait` to start the 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. diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 5f3c6b2..fc6ff5d 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -4,7 +4,8 @@ version: "3.4" services: php: build: - target: app_php_dev + context: . + target: php_dev volumes: - ./:/srv/app - ./docker/php/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro @@ -12,17 +13,22 @@ services: # from the bind-mount for better performance by enabling the next line: #- /srv/app/vendor environment: - # See https://xdebug.org/docs/all_settings#mode + # See https://xdebug.org/docs/all_settings#mode XDEBUG_MODE: "${XDEBUG_MODE:-off}" extra_hosts: # Ensure that host.docker.internal is correctly defined on Linux - host.docker.internal:host-gateway caddy: - command: ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile", "--watch"] + command: [ "caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile", "--watch" ] + build: + context: . + target: caddy_base volumes: - ./public:/srv/app/public:ro - ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro + environment: + MERCURE_EXTRA_DIRECTIVES: demo ###> symfony/mercure-bundle ### ###< symfony/mercure-bundle ### diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8c33061..6795790 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,11 +3,17 @@ version: "3.4" # Production environment override services: php: + build: + context: . + target: php_prod environment: APP_SECRET: ${APP_SECRET} MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET} caddy: + build: + context: . + target: caddy_prod environment: MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} diff --git a/docker-compose.yml b/docker-compose.yml index 10e21ff..8b99bc3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,12 +2,7 @@ version: "3.4" services: php: - build: - context: . - target: app_php - args: - SYMFONY_VERSION: ${SYMFONY_VERSION:-} - STABILITY: ${STABILITY:-stable} + image: ${IMAGES_PREFIX:-}app-php restart: unless-stopped volumes: - php_socket:/var/run/php @@ -17,6 +12,11 @@ services: retries: 3 start_period: 30s environment: + TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16} + TRUSTED_HOSTS: ^${SERVER_NAME:-example\.com|localhost}|caddy$$ + # The two next lines can be removed after initial installation + SYMFONY_VERSION: ${SYMFONY_VERSION:-} + STABILITY: ${STABILITY:-stable} # 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 @@ -25,13 +25,11 @@ services: MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} caddy: - build: - context: . - target: app_caddy + image: ${IMAGES_PREFIX:-}app-caddy depends_on: - php environment: - SERVER_NAME: ${SERVER_NAME:-localhost, caddy:80} + SERVER_NAME: ${SERVER_NAME:-localhost}, caddy:80 MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} restart: unless-stopped diff --git a/docker/php/docker-entrypoint.sh b/docker/php/docker-entrypoint.sh index 224f941..2843ec1 100755 --- a/docker/php/docker-entrypoint.sh +++ b/docker/php/docker-entrypoint.sh @@ -10,8 +10,6 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then # Install the project the first time PHP is started # After the installation, the following block can be deleted if [ ! -f composer.json ]; then - CREATION=1 - rm -Rf tmp/ composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install @@ -22,6 +20,11 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then cd - rm -Rf tmp/ + + if grep -q ^DATABASE_URL= .env; then + echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build --wait" + sleep infinity + fi fi if [ "$APP_ENV" != 'prod' ]; then @@ -29,12 +32,6 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then fi if grep -q ^DATABASE_URL= .env; then - # After the installation, the following block can be deleted - if [ "$CREATION" = "1" ]; then - echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build" - sleep infinity - fi - echo "Waiting for db to be ready..." ATTEMPTS_LEFT_TO_REACH_DATABASE=60 until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(bin/console dbal:run-sql "SELECT 1" 2>&1); do diff --git a/docs/build.md b/docs/build.md index c89d15d..aefc2f3 100644 --- a/docs/build.md +++ b/docs/build.md @@ -8,10 +8,10 @@ For instance, use the following command to install Symfony 5.4: On Linux: - SYMFONY_VERSION=5.4.* docker compose up --build + SYMFONY_VERSION=5.4.* docker compose up --wait On Windows: - set SYMFONY_VERSION=5.4.*&& docker compose up --build&set SYMFONY_VERSION= + set SYMFONY_VERSION=5.4.*&& docker compose up --wait&set SYMFONY_VERSION= ## Installing Development Versions of Symfony @@ -22,19 +22,17 @@ For instance, use the following command to use the development branch of Symfony On Linux: - STABILITY=dev docker compose up --build + STABILITY=dev docker compose up --wait + On Windows: - set STABILITY=dev&& docker compose up --build&set STABILITY= - + set STABILITY=dev&& docker compose up --wait&set STABILITY= ## Customizing the Server Name Use the `SERVER_NAME` environment variable to define your custom server name(s). - SERVER_NAME="app.localhost, caddy:80" docker compose up --build - -If you use Mercure, keep `caddy:80` in the list to allow the PHP container to request the caddy service. + SERVER_NAME="app.localhost" docker compose up --wait *Tips: You can define your server name variable in your `.env` file to keep it at each up* @@ -42,7 +40,7 @@ If you use Mercure, keep `caddy:80` in the list to allow the PHP container to re Use the environment variables `HTTP_PORT`, `HTTPS_PORT` and/or `HTTP3_PORT` to adjust the ports to your needs, e.g. - HTTP_PORT=8000 HTTPS_PORT=4443 HTTP3_PORT=4443 docker compose up --build + HTTP_PORT=8000 HTTPS_PORT=4443 HTTP3_PORT=4443 docker compose up --wait to access your application on [https://localhost:4443](https://localhost:4443). diff --git a/docs/gandi-dns.png b/docs/gandi-dns.png deleted file mode 100644 index b2a2377..0000000 Binary files a/docs/gandi-dns.png and /dev/null differ diff --git a/docs/production.md b/docs/production.md index db029d1..31f3ac5 100644 --- a/docs/production.md +++ b/docs/production.md @@ -31,7 +31,6 @@ ssh root@ In most cases, you'll want to associate a domain name to your website. If you don't own a domain name yet, you'll have to buy one through a registrar. -Use [this affiliate link](https://gandi.link/f/93650337) to redeem a 20% discount at Gandi.net. Then create a DNS record of type `A` for your domain name pointing to the IP address of your server. @@ -41,10 +40,6 @@ Example: your-domain-name.example.com. IN A 207.154.233.113 ```` -Example in Gandi's UI: - -![Creating a DNS record at Gandi.net](gandi-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. @@ -66,7 +61,7 @@ Go into the directory containing your project (``), and start the SERVER_NAME=your-domain-name.example.com \ APP_SECRET=ChangeMe \ CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \ -docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d +docker compose -f docker-compose.yml -f docker-compose.prod.yml up --wait ``` Be sure to replace `your-domain-name.example.com` by your actual domain name and to set the values of `APP_SECRET`, `CADDY_MERCURE_JWT_SECRET` to cryptographically secure random values. @@ -82,7 +77,7 @@ Alternatively, if you don't want to expose an HTTPS server but only an HTTP one, SERVER_NAME=:80 \ APP_SECRET=ChangeMe \ CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \ -docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d +docker compose -f docker-compose.yml -f docker-compose.prod.yml up --wait ``` ## Deploying on Multiple Nodes