diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..ee1e7eb --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +{ + "dockerComposeFile": [ + "./../docker-compose.yml", + "./../docker-compose.dev.yml" + ], + "service": "php", + "workspaceFolder": "/var/www/html", + "forwardPorts": ["caddy:80", "db:3306"], + "updateContentCommand": { + "composer install": "if [ -f composer.json ]; then composer install; fi;", + "yarn install": "if [ -f package.json ]; then yarn install --no-progress; fi;" + }, + "customizations": { + "vscode": { + "settings": { + "files.exclude": { + ".devcontainer/": true, + ".docker/": true, + "node_modules/": true, + "var/": true, + "vendor/": true, + ".dockerignore": true, + "docker-compose*": true, + "Dockerfile": true + } + } + } + } +} diff --git a/.docker/caddy/Caddyfile b/.docker/caddy/Caddyfile new file mode 100644 index 0000000..040d355 --- /dev/null +++ b/.docker/caddy/Caddyfile @@ -0,0 +1,16 @@ +{ + {$CADDY_ADMIN_OPTION} + {$CADDY_DEBUG_OPTION} +} + +{$SERVER_NAME} + +route { + root * /srv/public + vulcain + php_fastcgi unix//run/php/php-fpm.sock { + root /var/www/html/public + } + encode zstd gzip + file_server +} diff --git a/.docker/php/conf.d/app.ini b/.docker/php/conf.d/app.ini new file mode 100644 index 0000000..335a5cf --- /dev/null +++ b/.docker/php/conf.d/app.ini @@ -0,0 +1,6 @@ +apc.enable_cli = 1 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 +opcache.memory_consumption = 256 +realpath_cache_size = 4096K +realpath_cache_ttl = 600 diff --git a/.docker/php/conf.d/app.prod.ini b/.docker/php/conf.d/app.prod.ini new file mode 100644 index 0000000..a3f2376 --- /dev/null +++ b/.docker/php/conf.d/app.prod.ini @@ -0,0 +1 @@ +expose_php = Off diff --git a/.docker/php/php-fpm.d/zz-docker.conf b/.docker/php/php-fpm.d/zz-docker.conf new file mode 100644 index 0000000..29dc7c8 --- /dev/null +++ b/.docker/php/php-fpm.d/zz-docker.conf @@ -0,0 +1,5 @@ +[global] +daemonize = no + +[www] +listen = /run/php/php-fpm.sock diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..95b9300 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +**/.gitignore +*.md +.devcontainer/ +.git/ +.idea/ +.vscode/ +node_modules/ +var/ +vendor/ +.dockerignore +.gitignore +docker-compose.* +Dockerfile diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml new file mode 100644 index 0000000..58fa37f --- /dev/null +++ b/.github/workflows/make-release.yml @@ -0,0 +1,23 @@ +name: Release + +on: + push: + branches: + - main + paths-ignore: + - LICENSE + - README.md + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: marvinpinto/action-automatic-releases@latest + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + automatic_release_tag: latest + prerelease: true + title: "Development Build" + files: | + LICENSE + Dockerfile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb6c060 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.idea/ +/.vscode/ +/node_modules/ +/var/ +/vendor/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d46d692 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,151 @@ +# Build args +ARG CADDY_VERSION=2 +ARG PHP_VERSION=8.2 +ARG NODE_VERSION=lts +ARG COMPOSER_VERSION=latest + +ARG TIMEZONE=UTC + +############################################################################### +### [STAGE] caddy_builder +############################################################################### +FROM caddy:${CADDY_VERSION}-builder-alpine AS caddy_builder + +RUN xcaddy build \ + --with github.com/dunglas/vulcain \ + --with github.com/dunglas/vulcain/caddy + +############################################################################### +# [STAGE] node_builder +############################################################################### +FROM node:${NODE_VERSION}-alpine as node_builder +WORKDIR /app + +COPY package.json[n] package-lock.jso[n] yarn.loc[k] webpack.config.j[s] .npmr[c] .yarnr[c] ./ + + # OS configuration +RUN mkdir -p public/build; \ + # Node dependencies installation + if [ -f package.json ]; then \ + if [ -f yarn.lock ]; then \ + yarn install --no-progress; \ + else \ + npm install --no-progress; \ + fi; \ + fi; + +COPY assets/ assets/ + # Node.js dependencies building +RUN if [ -f node_modules/.bin/encore ]; then \ + mkdir -p public/build; \ + node_modules/.bin/encore production; \ + fi; + +############################################################################### +### [STAGE] composer_builder +############################################################################### +FROM composer:${COMPOSER_VERSION} as composer_builder + +############################################################################### +# [STAGE] php_builder +############################################################################### +FROM php:${PHP_VERSION}-fpm-bullseye as php_builder + +ARG TIMEZONE + +ENV COMPOSER_ALLOW_SUPERUSER=1 +ENV COMPOSER_MEMORY_LIMIT=-1 + + # OS packages installation +RUN apt-get -yqq update && apt-get -yqq install unzip; \ + # OS configuration + mkdir -p /run/php; \ + # PHP configuration + echo "date.timezone = ${TIMEZONE}" >> ${PHP_INI_DIR}/conf.d/timezone.ini; \ + # Cleanup + rm -rf /var/lib/apt/lists/* + +COPY --from=mlocati/php-extension-installer:latest /usr/bin/install-php-extensions /usr/local/bin +RUN install-php-extensions apcu gd intl opcache pdo_mysql xsl zip + +COPY --from=composer_builder /usr/bin/composer /usr/local/bin + +############################################################################### +# [STAGE] php_prod +############################################################################### +FROM php_builder AS php_prod + + # PHP configuration +RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini; \ + if [ -f config/preload.php ]; then \ + echo "opcache.preload_user = root" >> ${PHP_INI_DIR}/conf.d/preload.ini; \ + echo "opcache.preload = /var/www/html/config/preload.php" >> ${PHP_INI_DIR}/conf.d/preload.ini; \ + fi + +COPY .docker/php/conf.d/app.ini ${PHP_INI_DIR}/conf.d/zz-app.ini +COPY .docker/php/conf.d/app.prod.ini ${PHP_INI_DIR}/conf.d/zz-app.prod.ini +COPY .docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf + +COPY composer.* symfony.* ./ + + # Composer dependecies installation and autoload optimizations +RUN if [ -f composer.json ]; then \ + composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress; \ + composer dump-autoload --classmap-authoritative --no-dev; \ + fi + +COPY . . + + # Composer dump-env and post-install-cmd +RUN if [ -f composer.json ]; then \ + composer dump-env prod; \ + composer run-script --no-dev post-install-cmd; \ + fi; \ + # Cleanup + rm -rf .docker/; \ + rm -f /usr/bin/install-php-extensions; \ + composer clear-cache + +############################################################################### +# [STAGE] php_dev +############################################################################### +FROM php_builder AS php_dev + + # OS packages installation +RUN apt-get update && apt-get install -y git openssh-client; \ + # PHP configuration + cp ${PHP_INI_DIR}/php.ini-development ${PHP_INI_DIR}/php.ini; \ + # PHP extension installation + install-php-extensions xdebug; \ + # Node.js installation + curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -; \ + apt-get install -y nodejs; \ + npm update -g npm; \ + npm install -g yarn; \ + # Cleanup + rm -rf /root/.npm; \ + rm -f .env.local.php + +############################################################################### +# [STAGE] caddy_base +############################################################################### +FROM caddy:${CADDY_VERSION}-alpine as caddy_base + + # OS packages installation +RUN apk add --no-cache tzdata; + +COPY --from=caddy_builder /usr/bin/caddy /usr/bin/caddy +COPY .docker/caddy/Caddyfile /etc/caddy/Caddyfile + +############################################################################### +# [STAGE] caddy_dev +############################################################################### +FROM caddy_base as caddy_dev + +############################################################################### +# [STAGE] caddy_prod +############################################################################### +FROM caddy_base as caddy_prod + +COPY public/ public/ +COPY --from=node_builder /app/public/build public/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..35889e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Marco Polichetti + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e87c388 --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +# 🚀 Symfony Sail + +Like the name suggest, inspired by Laravel Sail, this is a complete **development and production ready environment** for Symfony 6 projects, based on Docker containers. + +> This project is largely inspired by the great work of [dunglas/symfony-docker](https://github.com/dunglas/symfony-docker) (thank you for that). It comes with less feature but it should be easier to use for most users. And includes Node.js, because... why not? + +## ⚙️ Controlling the environment + +> **Note**: other environment files like `.env.local` are **not** taken into account. Only the main file is used. + +The build args (that is, the environment) can be change setting variables in the `.env` file: + +| Variable | Allowed values | Default value | +| :------------------- | :----------------------------------- | :-----------------: | +| `CADDY_VERSION` | `x`, `x.y`, `x.y.z` | `2` | +| `COMPOSER_VERSION` | `latest`, `lts`, `x`, `x.y`, `x.y.z` | `latest` | +| `NODE_VERSION` | `current`, `lts`, `x` | `lts` | +| `PHP_VERSION` | `x`, `x.y`, `x.y.z` | `8.2` | + +Other options: + +| Variable | Allowed values | Default value | +| :------------------- | :----------------------------------- | :-----------------: | +| `CADDY_ADMIN_OPTION` | Caddy global `admin` option | *Environment based* | +| `CADDY_DEBUG_OPTION` | Caddy global `debug` option | *Environment based* | + + +## 🐋 Docker internals + +Project [`Dockerfile`](Dockerfile) is a [multi-stage build](https://docs.docker.com/build/building/multi-stage/) where multiple `FROM` statements are used in order to build the final artifact(s). + +### Development environment flow + +The development flow involves two Docker compose files: + +- [`docker-compose.yml`](docker-compose.yml) +- [`docker-compose.dev.yml`](docker-compose.dev.yml) + +> **Note**: Both files are loaded automatically by Visual Studio Code dev [container descriptor](.devcontainer/devcontainer.json). + +```mermaid +stateDiagram + caddy_base: [FROM] caddy_base + note right of caddy_base + CADDY_VERSION + end note + caddy_builder: [FROM] caddy_builder + note right of caddy_builder + CADDY_VERSION + end note + composer_builder: [FROM] composer_builder + note left of composer_builder + COMPOSER_VERSION + end note + php_builder: [FROM] php_builder + note left of php_builder + PHP_VERSION + end note + php_extension_installer: [FROM] mlocati/php-extension-installer + + caddy_dev: [TARGET] caddy_dev + php_dev: [TARGET] php_dev + + [*] --> caddy + [*] --> php + + state caddy { + caddy_builder --> caddy_base + } + caddy --> caddy_dev + + state php { + php_extension_installer --> php_builder + composer_builder --> php_builder + } + php --> php_dev + + php_dev --> [*] + note left of php_dev + NODE_VERSION + end note + caddy_dev --> [*] +``` + +### Production environment flow + +The production flow involves two Docker compose files: + +- [`docker-compose.yml`](docker-compose.yml) +- [`docker-compose.override.yml`](docker-compose.override.yml) + +> **Note**: Docker compose will load both files automatically. In other words, there is no need to specify `-f` when calling `docker compose`. + +```mermaid +stateDiagram + caddy_base: [FROM] caddy_base + note left of caddy_base + CADDY_VERSION + end note + caddy_builder: [FROM] caddy_builder + note left of caddy_builder + CADDY_VERSION + end note + composer_builder: [FROM] composer_builder + note right of composer_builder + COMPOSER_VERSION + end note + node_builder: [FROM] node_builder + note left of node_builder + NODE_VERSION + end note + php_builder: [FROM] php_builder + note left of php_builder + PHP_VERSION + end note + php_extension_installer: [FROM] mlocati/php-extension-installer + + caddy_prod: [TARGET] caddy_prod + php_prod: [TARGET] php_prod + + [*] --> caddy + [*] --> php + + state caddy { + caddy_builder --> caddy_base + } + caddy --> caddy_prod + node_builder --> caddy_prod + + state php { + php_extension_installer --> php_builder + composer_builder --> php_builder + } + php --> php_prod + + php_prod --> [*] + caddy_prod --> [*] +``` diff --git a/assets/.gitignore b/assets/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..cf49832 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,30 @@ +services: + caddy: + image: ${COMPOSE_PROJECT_NAME}-caddy-dev + build: + target: caddy_dev + environment: + CADDY_ADMIN_OPTION: ${CADDY_ADMIN_OPTION:-} + CADDY_DEBUG_OPTION: ${CADDY_DEBUG_OPTION:-debug} + volumes: + - ./.docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - ./public/:/srv/public + + php: + image: ${COMPOSE_PROJECT_NAME}-php-dev + build: + target: php_dev + args: + NODE_VERSION: ${NODE_VERSION} + volumes: + - ./.:/var/www/html + - ./.docker/php/conf.d/app.ini:/usr/local/etc/php/conf.d/zz-app.ini:ro + - ./.docker/php/php-fpm.d/zz-docker.conf:/usr/local/etc/php-fpm.d/zz-docker.conf:ro + - node_modules:/var/www/html/node_modules + - var:/var/www/html/var + - vendor:/var/www/html/vendor + +volumes: + node_modules: + var: + vendor: diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..2803493 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,19 @@ +services: + caddy: + image: ${COMPOSE_PROJECT_NAME}-caddy-prod + build: + target: caddy_prod + args: + NODE_VERSION: ${NODE_VERSION} + environment: + CADDY_ADMIN_OPTION: ${CADDY_ADMIN_OPTION:-admin off} + CADDY_DEBUG_OPTION: ${CADDY_DEBUG_OPTION:-} + ports: + - "80:80" + - "443:443" + - "443:443/udp" + + php: + image: ${COMPOSE_PROJECT_NAME}-php-prod + build: + target: php_prod diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9797fd6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +version: "3.8" + +services: + caddy: + build: + context: ./ + args: + CADDY_VERSION: ${CADDY_VERSION} + environment: + SERVER_NAME: ${SERVER_NAME:-":80"} + TZ: ${TZ:-UTC} + volumes: + - caddy_data:/data + - caddy_config:/config + - php_socket:/run/php + php: + build: + context: ./ + args: + PHP_VERSION: ${PHP_VERSION} + COMPOSER_VERSION: ${COMPOSER_VERSION} + TIMEZONE: ${TZ:-UTC} + environment: + TZ: ${TZ:-UTC} + volumes: + - php_socket:/run/php + + db: + image: mariadb:10.7 + volumes: + - db_data:/var/lib/mysql + environment: + TZ: ${TZ:-UTC} + MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "true" + +volumes: + caddy_config: + caddy_data: + php_socket: + db_data: diff --git a/public/.gitignore b/public/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..390cd28 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..83f1549 --- /dev/null +++ b/public/index.php @@ -0,0 +1,3 @@ +