From 9bcfdd9e1f9dafa5e21c3a1b9e0789e4710383dd Mon Sep 17 00:00:00 2001 From: Mathieu Piot Date: Mon, 14 Jan 2019 19:40:41 +0100 Subject: [PATCH] Implement Docker --- .gitignore | 4 + .php_cs.dist | 1 + Dockerfile | 181 +++++++++++++++++++++++ Makefile | 173 ++++++++++++++++++++++ docker-compose.override.yml.dist | 11 ++ docker-compose.yml | 52 +++++++ docker/MysqlDockerfile | 8 + docker/NginxDockerfile | 41 +++++ src/Migrations/Version20190113114527.php | 46 ++++++ 9 files changed, 517 insertions(+) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 docker-compose.override.yml.dist create mode 100644 docker-compose.yml create mode 100644 docker/MysqlDockerfile create mode 100644 docker/NginxDockerfile create mode 100644 src/Migrations/Version20190113114527.php diff --git a/.gitignore b/.gitignore index 7d5655849a..dcd594ffed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ /public/build/fonts/glyphicons-* /public/build/images/glyphicons-* +###> docker ### +docker-compose.override.yml +###< docker ### + ###> symfony/framework-bundle ### /.env.local /.env.*.local diff --git a/.php_cs.dist b/.php_cs.dist index edafec3afe..d94c8dcc66 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -12,6 +12,7 @@ COMMENT; $finder = PhpCsFixer\Finder::create() ->in(__DIR__) ->exclude('config') + ->exclude('src/Migrations') ->exclude('var') ->exclude('public/bundles') ->exclude('public/build') diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..647066ac80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,181 @@ +ARG NODE_VERSION=11.6.0 +ARG COMPOSER_VERSION=1.8.0 +ARG PHP_VERSION=7.2.13 +ARG ICU_VERSION=63.1 +ARG APCU_VERSION=5.1.16 +ARG XDEBUG_VERSION=2.6.1 + + +##################################### +## APP ## +##################################### +FROM php:${PHP_VERSION}-fpm as app + +ARG ICU_VERSION +ARG APCU_VERSION + +# Used for the ICU compilation +ENV PHP_CPPFLAGS="${PHP_CPPFLAGS} -std=c++11" +ENV APP_VERSION=0.0.0 + +WORKDIR /app + +# Install paquet requirements +RUN set -ex; \ + # Install required system packages + apt-get update; \ + apt-get install -qy --no-install-recommends \ + zlib1g-dev \ + git \ + ; \ + # Compile ICU (required by intl php extension) + curl -L -o /tmp/icu.tar.gz http://download.icu-project.org/files/icu4c/${ICU_VERSION}/icu4c-$(echo ${ICU_VERSION} | sed s/\\./_/g)-src.tgz; \ + tar -zxf /tmp/icu.tar.gz -C /tmp; \ + cd /tmp/icu/source; \ + ./configure --prefix=/usr/local; \ + make clean; \ + make; \ + make install; \ + #Install the PHP extensions + docker-php-ext-configure intl --with-icu-dir=/usr/local; \ + docker-php-ext-install -j "$(nproc)" \ + intl \ + pdo \ + # pdo_mysql \ Uncomment it to use MySQL, and remove the pdo_sqlite (see: docker-compose.yml, docker-compose.override.yml.dist) + zip \ + bcmath \ + ; \ + pecl install \ + apcu-${APCU_VERSION} \ + ; \ + docker-php-ext-enable \ + opcache \ + apcu \ + ; \ + docker-php-source delete; \ + # Clean aptitude cache and tmp directory + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*; + +## set recommended PHP.ini settings +RUN { \ + echo 'date.timezone = Europe/Paris'; \ + echo 'short_open_tag = off'; \ + echo 'expose_php = off'; \ + echo 'error_log = /proc/self/fd/2'; \ + echo 'memory_limit = 128m'; \ + echo 'post_max_size = 110m'; \ + echo 'upload_max_filesize = 100m'; \ + echo 'opcache.enable = 1'; \ + echo 'opcache.enable_cli = 1'; \ + echo 'opcache.memory_consumption = 256'; \ + echo 'opcache.interned_strings_buffer = 16'; \ + echo 'opcache.max_accelerated_files = 20011'; \ + echo 'opcache.fast_shutdown = 1'; \ + echo 'realpath_cache_size = 4096K'; \ + echo 'realpath_cache_ttl = 600'; \ + } > /usr/local/etc/php/php.ini + +RUN { \ + echo 'date.timezone = Europe/Paris'; \ + echo 'short_open_tag = off'; \ + echo 'memory_limit = 8192M'; \ + } > /usr/local/etc/php/php-cli.ini + +CMD ["php-fpm"] + + +##################################### +## APP DEV ## +##################################### +FROM app as app-dev + +ARG NODE_VERSION +ARG COMPOSER_VERSION +ARG XDEBUG_VERSION + +ENV COMPOSER_ALLOW_SUPERUSER=1 +ENV APP_ENV=dev + +# Install paquet requirements +RUN set -ex; \ + # Install required system packages + apt-get update; \ + apt-get install -qy --no-install-recommends \ + unzip \ + ; \ + # Clean aptitude cache and tmp directory + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*; + +# Install Node +RUN set -ex; \ + curl -L -o /tmp/nodejs.tar.gz https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz; \ + tar xfvz /tmp/nodejs.tar.gz -C /usr/local --strip-components=1; \ + rm -f /tmp/nodejs.tar.gz; \ + npm install yarn -g + +# Install Composer +RUN set -ex; \ + EXPECTED_SIGNATURE="$(curl -L https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar.sha256sum)"; \ + curl -L -o composer.phar https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar; \ + ACTUAL_SIGNATURE="$(sha256sum composer.phar)"; \ + if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]; then >&2 echo 'ERROR: Invalid installer signature' && rm /usr/local/bin/composer && exit 1 ; fi; \ + chmod +x composer.phar && mv composer.phar /usr/local/bin/composer; \ + RESULT=$?; \ + exit $RESULT; + +# Edit OPCache configuration +RUN set -ex; \ + { \ + echo 'opcache.validate_timestamps = 1'; \ + echo 'opcache.revalidate_freq = 0'; \ + } >> /usr/local/etc/php/php.ini + +# Install Xdebug +RUN set -ex; \ + if [ "${XDEBUG_VERSION}" != 0 ]; \ + then \ + pecl install xdebug-${XDEBUG_VERSION}; \ + docker-php-ext-enable xdebug; \ + { \ + echo 'xdebug.remote_enable = on'; \ + echo 'xdebug.remote_connect_back = on'; \ + } >> /usr/local/etc/php/php.ini \ + ; fi + + +##################################### +## PROD ASSETS BUILDER ## +##################################### +FROM node:${NODE_VERSION} as assets-builder + +COPY . /app +WORKDIR /app + +RUN yarn install && yarn build && rm -R node_modules + +##################################### +## PROD VENDOR BUILDER ## +##################################### +FROM composer:${COMPOSER_VERSION} as vendor-builder + +COPY --from=assets-builder /app /app +WORKDIR /app + +RUN APP_ENV=prod composer install -o -n --no-ansi --no-dev + + +##################################### +## APP PROD ## +##################################### +FROM app as app-prod + +ENV APP_ENV=prod + +COPY --from=vendor-builder /app /app +WORKDIR /app + +# Edit OPCache configuration +RUN set -ex; \ + { \ + echo 'opcache.validate_timestamps = 0'; \ + } >> /usr/local/etc/php/php.ini diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..f15ca685c8 --- /dev/null +++ b/Makefile @@ -0,0 +1,173 @@ +DOCKER_COMPOSE?=docker-compose +EXEC?=$(DOCKER_COMPOSE) exec app +CONSOLE=bin/console +PHPCSFIXER?=$(EXEC) php -d memory_limit=1024m vendor/bin/php-cs-fixer + +.DEFAULT_GOAL := help +.PHONY: help start stop restart install uninstall reset clear-cache tty clear clean +.PHONY: db-diff db-migrate db-rollback db-reset db-validate wait-for-db +.PHONY: watch assets assets-build +.PHONY: tests lint lint-symfony lint-yaml lint-twig lint-twig php-cs php-cs-fix security-check test-schema test-all +.PHONY: deps +.PHONY: build up perm +.PHONY: docker-compose.override.yml + +help: + @grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' + +## +## Project setup +##--------------------------------------------------------------------------- + +start: ## Start docker containers + $(DOCKER_COMPOSE) start + +stop: ## Stop docker containers + $(DOCKER_COMPOSE) stop + +restart: ## Restart docker containers + $(DOCKER_COMPOSE) restart + +install: docker-compose.override.yml build up deps perm ## Create and start docker containers + +uninstall: stop ## Remove docker containers + $(DOCKER_COMPOSE) rm -vf + +reset: uninstall install ## Remove and re-create docker containers + +clear-cache: perm + $(EXEC) $(CONSOLE) cache:clear --no-warmup + $(EXEC) $(CONSOLE) cache:warmup + +tty: ## Run app container in interactive mode + $(EXEC) /bin/bash + +clear: perm ## Remove all the cache, the logs, the sessions and the built assets + $(EXEC) rm -rf var/cache/* + $(EXEC) $(CONSOLE) redis:flushall -n + rm -rf var/log/* + rm -rf public/build + rm -f var/.php_cs.cache + +clean: clear ## Clear and remove dependencies + rm -rf vendor node_modules + + +## +## Database +##--------------------------------------------------------------------------- + +wait-for-db: + $(EXEC) php -r "set_time_limit(60);for(;;){if(@fsockopen('db',3306)){break;}echo \"Waiting for MySQL\n\";sleep(1);}" + +db-diff: vendor wait-for-db ## Generate a migration by comparing your current database to your mapping information + $(EXEC) $(CONSOLE) doctrine:migration:diff + +db-migrate: vendor wait-for-db ## Migrate database schema to the latest available version + $(EXEC) $(CONSOLE) doctrine:migration:migrate -n + +db-rollback: vendor wait-for-db ## Rollback the latest executed migration + $(EXEC) $(CONSOLE) doctrine:migration:migrate prev -n + +db-reset: vendor wait-for-db ## Reset the database + $(EXEC) $(CONSOLE) doctrine:database:drop --force --if-exists + $(EXEC) $(CONSOLE) doctrine:database:create --if-not-exists + $(EXEC) $(CONSOLE) doctrine:migrations:migrate -n + +db-fixtures: vendor wait-for-db ## Apply doctrine fixtures + $(EXEC) $(CONSOLE) doctrine:fixtures:load -n + +db-validate: vendor wait-for-db ## Check the ORM mapping + $(EXEC) $(CONSOLE) doctrine:schema:validate + + +## +## Assets +##--------------------------------------------------------------------------- + +watch: node_modules ## Watch the assets and build their development version on change + $(EXEC) yarn watch + +assets: node_modules ## Build the development version of the assets + $(EXEC) yarn dev + +assets-build: node_modules ## Build the production version of the assets + $(EXEC) yarn build + +## +## Tests +##--------------------------------------------------------------------------- + +tests: ## Run all the PHP tests + $(EXEC) bin/phpunit + +lint: lint-symfony php-cs ## Run lint on Twig, YAML, PHP and Javascript files + +lint-symfony: lint-yaml lint-twig lint-xliff ## Lint Symfony (Twig and YAML) files + +lint-yaml: ## Lint YAML files + $(EXEC) $(CONSOLE) lint:yaml config + +lint-twig: ## Lint Twig files + $(EXEC) $(CONSOLE) lint:twig templates + +lint-xliff: ## Lint Translation files + $(EXEC) $(CONSOLE) lint:xliff translations + +php-cs: vendor ## Lint PHP code + $(PHPCSFIXER) fix --diff --dry-run --no-interaction -v + +php-cs-fix: vendor ## Lint and fix PHP code to follow the convention + $(PHPCSFIXER) fix + +security-check: vendor ## Check for vulnerable dependencies + $(EXEC) vendor/bin/security-checker security:check + +test-schema: vendor ## Test the doctrine Schema + $(EXEC) $(CONSOLE) doctrine:schema:validate --skip-sync -vvv --no-interaction + +test-all: lint test-schema security-check tests ## Lint all, check vulnerable dependencies, run PHP tests + +## +## Dependencies +##--------------------------------------------------------------------------- + +deps: vendor assets ## Install the project PHP and JS dependencies + + +## + + +# Internal rules + +build: + $(DOCKER_COMPOSE) pull --ignore-pull-failures + $(DOCKER_COMPOSE) build --force-rm + +up: + $(DOCKER_COMPOSE) up -d --remove-orphans + +perm: + $(EXEC) chmod -R 777 var public/build node_modules vendor + $(EXEC) chown -R www-data:root var public/build node_modules vendor + +docker-compose.override.yml: +ifneq ($(wildcard docker-compose.override.yml),docker-compose.override.yml) + @echo docker-compose.override.yml do not exists, copy docker-compose.override.yml.dist to create it, and fill it. + exit 1 +endif + + +# Rules from files + +vendor: composer.lock + $(EXEC) composer install -n + +composer.lock: composer.json + @echo compose.lock is not up to date. + +node_modules: yarn.lock + $(EXEC) yarn install + +yarn.lock: package.json + @echo yarn.lock is not up to date. diff --git a/docker-compose.override.yml.dist b/docker-compose.override.yml.dist new file mode 100644 index 0000000000..de184ecee5 --- /dev/null +++ b/docker-compose.override.yml.dist @@ -0,0 +1,11 @@ +version: '3.4' + +services: + nginx: + ports: + - 127.0.0.1:8080:80 + +# Uncomment it, if you want to use MySQL (see: Dockerfile, docker-compose.yml) +# db: +# ports: +# - 127.0.0.1:3306:3306 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..b20b9611bf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +version: '3.4' + +services: + nginx: + build: + context: docker + dockerfile: NginxDockerfile + depends_on: + - app + networks: + - frontend + volumes: + - .:/app + + app: + build: + context: . + target: app-dev +# Uncomment if you want to use MySQL (see: Dockerfile, docker-compose.override.yml) +# depends_on: +# - db + networks: + - frontend +# Uncomment if you want to use MySQL (see: Dockerfile, docker-compose.override.yml) +# - backend + volumes: + - .:/app + +# Uncomment if you want to use MySQL (see: Dockerfile, docker-compose.override.yml) +# db: +# build: +# context: docker +# dockerfile: MysqlDockerfile +# environment: +# - MYSQL_ROOT_PASSWORD=symfony-demo +# - MYSQL_USER=symfony-demo +# - MYSQL_PASSWORD=symfony-demo +# - MYSQL_DATABASE=symfony-demo +# volumes: +# - db_data:/var/lib/mysql +# networks: +# - backend + +# Uncomment if you want to use MySQL (see: Dockerfile, docker-compose.override.yml) +#volumes: +# db_data: +# driver: local + +networks: + frontend: +# Uncomment if you want to use MySQL (see: Dockerfile, docker-compose.override.yml) +# backend: diff --git a/docker/MysqlDockerfile b/docker/MysqlDockerfile new file mode 100644 index 0000000000..5c7d04190b --- /dev/null +++ b/docker/MysqlDockerfile @@ -0,0 +1,8 @@ +FROM mysql:8.0.13 + +# set MySQL config +RUN rm /etc/mysql/conf.d/mysql.cnf +RUN { \ + echo '[mysqld]'; \ + echo 'default_authentication_plugin= mysql_native_password'; \ + } > /etc/mysql/conf.d/mysql.cnf diff --git a/docker/NginxDockerfile b/docker/NginxDockerfile new file mode 100644 index 0000000000..ab67424a4a --- /dev/null +++ b/docker/NginxDockerfile @@ -0,0 +1,41 @@ +FROM nginx:1.15.8 + +# set nginx config +RUN rm /etc/nginx/conf.d/default.conf +RUN { \ + echo 'server {'; \ + echo ' listen 80;'; \ + echo ' server_name _;'; \ + echo ' root /app/public;'; \ + echo ''; \ + echo ' add_header X-Content-Type-Options nosniff;'; \ + echo ' add_header X-XSS-Protection "1; mode=block";'; \ + echo ' add_header X-Frame-Options SAMEORIGIN;'; \ + echo ''; \ + echo ' if ($http_user_agent ~* "WordPress") {'; \ + echo ' return 403;'; \ + echo ' }'; \ + echo ''; \ + echo ' location / {'; \ + echo ' try_files $uri /index.php$is_args$args;'; \ + echo ' }'; \ + echo ''; \ + echo ' location /protected-files/ {'; \ + echo ' internal;'; \ + echo ' alias /app/files/;'; \ + echo ' }'; \ + echo ''; \ + echo ' location ~ ^/index\.php(/|$) {'; \ + echo ' fastcgi_pass app:9000;'; \ + echo ' fastcgi_split_path_info ^(.+\.php)(/.*)$;'; \ + echo ' include fastcgi_params;'; \ + echo ' fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;'; \ + echo ' fastcgi_param DOCUMENT_ROOT $realpath_root;'; \ + echo ' internal;'; \ + echo ' }'; \ + echo ''; \ + echo ' location ~ \.php$ {'; \ + echo ' return 404;'; \ + echo ' }'; \ + echo '}'; \ + } > /etc/nginx/conf.d/default.conf diff --git a/src/Migrations/Version20190113114527.php b/src/Migrations/Version20190113114527.php new file mode 100644 index 0000000000..524c98b5fb --- /dev/null +++ b/src/Migrations/Version20190113114527.php @@ -0,0 +1,46 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE TABLE symfony_demo_tag (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_4D5855405E237E06 (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_user (id INT AUTO_INCREMENT NOT NULL, full_name VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', UNIQUE INDEX UNIQ_8FB094A1F85E0677 (username), UNIQUE INDEX UNIQ_8FB094A1E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_comment (id INT AUTO_INCREMENT NOT NULL, post_id INT NOT NULL, author_id INT NOT NULL, content LONGTEXT NOT NULL, published_at DATETIME NOT NULL, INDEX IDX_53AD8F834B89032C (post_id), INDEX IDX_53AD8F83F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_post (id INT AUTO_INCREMENT NOT NULL, author_id INT NOT NULL, title VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, summary VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, published_at DATETIME NOT NULL, INDEX IDX_58A92E65F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('CREATE TABLE symfony_demo_post_tag (post_id INT NOT NULL, tag_id INT NOT NULL, INDEX IDX_6ABC1CC44B89032C (post_id), INDEX IDX_6ABC1CC4BAD26311 (tag_id), PRIMARY KEY(post_id, tag_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('ALTER TABLE symfony_demo_comment ADD CONSTRAINT FK_53AD8F834B89032C FOREIGN KEY (post_id) REFERENCES symfony_demo_post (id)'); + $this->addSql('ALTER TABLE symfony_demo_comment ADD CONSTRAINT FK_53AD8F83F675F31B FOREIGN KEY (author_id) REFERENCES symfony_demo_user (id)'); + $this->addSql('ALTER TABLE symfony_demo_post ADD CONSTRAINT FK_58A92E65F675F31B FOREIGN KEY (author_id) REFERENCES symfony_demo_user (id)'); + $this->addSql('ALTER TABLE symfony_demo_post_tag ADD CONSTRAINT FK_6ABC1CC44B89032C FOREIGN KEY (post_id) REFERENCES symfony_demo_post (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE symfony_demo_post_tag ADD CONSTRAINT FK_6ABC1CC4BAD26311 FOREIGN KEY (tag_id) REFERENCES symfony_demo_tag (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE symfony_demo_post_tag DROP FOREIGN KEY FK_6ABC1CC4BAD26311'); + $this->addSql('ALTER TABLE symfony_demo_comment DROP FOREIGN KEY FK_53AD8F83F675F31B'); + $this->addSql('ALTER TABLE symfony_demo_post DROP FOREIGN KEY FK_58A92E65F675F31B'); + $this->addSql('ALTER TABLE symfony_demo_comment DROP FOREIGN KEY FK_53AD8F834B89032C'); + $this->addSql('ALTER TABLE symfony_demo_post_tag DROP FOREIGN KEY FK_6ABC1CC44B89032C'); + $this->addSql('DROP TABLE symfony_demo_tag'); + $this->addSql('DROP TABLE symfony_demo_user'); + $this->addSql('DROP TABLE symfony_demo_comment'); + $this->addSql('DROP TABLE symfony_demo_post'); + $this->addSql('DROP TABLE symfony_demo_post_tag'); + } +}