{"id":1883,"date":"2019-06-10T00:00:00","date_gmt":"2019-06-09T22:00:00","guid":{"rendered":"https:\/\/wwwneu.strehle.de\/tim\/weblog\/archives\/2019\/06\/10\/1642-2\/"},"modified":"2025-07-31T21:36:14","modified_gmt":"2025-07-31T19:36:14","slug":"1642-2","status":"publish","type":"post","link":"https:\/\/www.strehle.de\/tim\/weblog\/archives\/2019\/06\/10\/1642-2\/","title":{"rendered":"First steps: A Symfony 4 PHP app in Docker"},"content":{"rendered":"\n<p>I\u2019m currently learning <a href=\"https:\/\/www.docker.com\/\">Docker<\/a>, and finding out how to develop a (new) <a href=\"https:\/\/symfony.com\">Symfony<\/a> 4 (PHP 7) application locally on my Mac and run it within a Docker container, instead of hosting it in a Linux VM in VMware Fusion as I used to do. <a href=\"https:\/\/www.docker.com\/products\/docker-desktop\">Docker Desktop<\/a> and the <a href=\"https:\/\/www.jetbrains.com\/phpstorm\/\">PhpStorm IDE<\/a> are already installed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Docker setup<\/h3>\n\n\n\n<p>I want to use official, current Docker images from the <a href=\"https:\/\/hub.docker.com\">Docker Hub<\/a>. A colleague convinced me (better scalability?) to use <a href=\"https:\/\/nginx.org\">Nginx<\/a> instead of my trusty old Apache Web server. So I need the <a href=\"https:\/\/hub.docker.com\/_\/nginx\">nginx:latest<\/a> and <a href=\"https:\/\/hub.docker.com\/_\/php\">php:fpm<\/a> images.<\/p>\n\n\n\n<p>While I can use the Nginx image as-is, I need to create my own image on top of the PHP standard one since I will want to add PHP extensions as needed, and to run <a href=\"https:\/\/getcomposer.org\">Composer<\/a> in the container. I ended up with <a href=\"https:\/\/github.com\/tistre\/symfony-in-docker-example\/blob\/master\/docker\/php\/Dockerfile\">this <code>Dockerfile<\/code><\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM php:fpm\n\n# git and unzip for composer installs\nRUN apt-get update &amp;&amp; apt-get install -y \\\n&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; git unzip \\\n&nbsp;&nbsp;&nbsp; --no-install-recommends &amp;&amp; rm -r \/var\/lib\/apt\/lists\/*\n\n# zip for composer installs\nRUN apt-get update &amp;&amp; apt-get install -y \\\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libzip-dev \\\n&nbsp;&nbsp;&nbsp; &amp;&amp; docker-php-ext-install zip\n\nENV COMPOSER_HOME \/tmp\nENV COMPOSER_VERSION 1.8.5\n\n# \"chmod 0777 \/tmp\/cache\" for non-root composer installs\nRUN curl --silent --fail --location --retry 3 --output \/tmp\/installer.php \\\n  --url https:\/\/raw.githubusercontent.com\/composer\/getcomposer.org\/cb19\u2026\/web\/installer \\\n&nbsp;&amp;&amp; php -r \" \\\n&nbsp;&nbsp;&nbsp; \\$signature = '48e3\u2026'; \\\n&nbsp;&nbsp;&nbsp; \\$hash = hash('sha384', file_get_contents('\/tmp\/installer.php')); \\\n&nbsp;&nbsp;&nbsp; if (!hash_equals(\\$signature, \\$hash)) { \\\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unlink('\/tmp\/installer.php'); \\\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \\\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(1); \\\n&nbsp;&nbsp;&nbsp; }\" \\\n&nbsp;&amp;&amp; php \/tmp\/installer.php --no-ansi --install-dir=\/usr\/bin --filename=composer \\\n  --version=${COMPOSER_VERSION} \\\n&nbsp;&amp;&amp; composer --ansi --version --no-interaction \\\n&nbsp;&amp;&amp; rm -f \/tmp\/installer.php \\\n&nbsp;&amp;&amp; chmod 0777 \/tmp\/cache\n<\/code><\/pre>\n\n\n\n<p><a href=\"https:\/\/github.com\/tistre\/symfony-in-docker-example\/blob\/master\/docker-compose.yml\">This <code>docker-compose.yml<\/code> file<\/a> defines my Nginx and PHP Docker setup:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>version: '3'\n\nservices:\n&nbsp;&nbsp;&nbsp; php:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; build:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context: .\/docker\/php\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; volumes:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - .\/app:\/opt\/app\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - .\/docker\/php\/zz-log.conf:\/usr\/local\/etc\/php-fpm.d\/zz-log.conf\n&nbsp;&nbsp;&nbsp; web:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; image: nginx:latest\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; depends_on:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - php\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ports:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - \"8080:80\"\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; volumes:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - .\/app:\/opt\/app\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - .\/docker\/web\/default.conf:\/etc\/nginx\/conf.d\/default.conf\n<\/code><\/pre>\n\n\n\n<p>The <code>context: .\/docker\/php<\/code> line refers to the directory where I put the Dockerfile shown above.<\/p>\n\n\n\n<p>My Symfony application source files reside in a local <code>app<\/code> directory on my Mac, which is empty for now, and mapped via <code>volumes<\/code> into both containers as <code>\/opt\/app<\/code>.<\/p>\n\n\n\n<p>See <a href=\"https:\/\/github.com\/tistre\/symfony-in-docker-example\">my Github repo<\/a> for the two configuration files \u2013 a tiny <a href=\"https:\/\/github.com\/tistre\/symfony-in-docker-example\/blob\/master\/docker\/php\/zz-log.conf\"><code>zz-log.conf<\/code> file<\/a> that configures PHP <code>log_errors<\/code> and <code>display_errors<\/code> settings, and the Nginx <a href=\"https:\/\/github.com\/tistre\/symfony-in-docker-example\/blob\/master\/docker\/web\/default.conf\"><code>default.conf<\/code> file<\/a> which contains configuration for Symfony, and hands over PHP requests to the PHP-FPM image.<\/p>\n\n\n\n<p>Nginx listens on port 80 inside the container; on my Mac, I can reach it at localhost:8080.<\/p>\n\n\n\n<p>Thanks to the fantastic <a href=\"https:\/\/www.jetbrains.com\/help\/phpstorm\/docker.html\">PhpStorm Docker support<\/a>, I can simply click the green \u201cdocker-compose up\u201d arrows that show up in <code>docker-compose.yml<\/code> in the IDE to start the Docker containers (downloading and building images when run for the first time):<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/s3.eu-central-1.amazonaws.com\/files.strehle.de\/tim\/blog\/2019-06-10-phpstorm-docker-compose-up.png\"><img decoding=\"async\" src=\"https:\/\/s3.eu-central-1.amazonaws.com\/files.strehle.de\/tim\/blog\/2019-06-10-phpstorm-docker-compose-up.png\" alt=\"PhpStorm IDE screenshot\"\/><\/a><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Creating a Symfony application<\/h3>\n\n\n\n<p>PhpStorm lets me \u201cExec\u201d commands within a Docker container, but I prefer my Mac\u2019s Terminal application. To discover the current container IDs (changing after a restart), I use <code>docker ps<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker ps\nCONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \n0b83693cc0ff&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nginx:latest&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"nginx -g 'daemon of\u2026\"&nbsp;&nbsp; \u2026\n9fade34cfdb7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; symfony-in-docker-example_php&nbsp;&nbsp; \"docker-php-entrypoi\u2026\"&nbsp;&nbsp; \u2026\n<\/code><\/pre>\n\n\n\n<p>Now I run <code>composer create-project<\/code> (as per the <a href=\"https:\/\/symfony.com\/doc\/current\/setup.html\">Symfony documentation<\/a>) in the PHP container to create a Symfony application in the local <code>app<\/code> directory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker exec --workdir \/opt --user daemon:daemon -it 9fade34cfdb7 \\\n  composer create-project symfony\/skeleton app\nInstalling symfony\/skeleton (v4.3.1.1)\n&nbsp; - Installing symfony\/skeleton (v4.3.1.1): Downloading (100%)&nbsp; \n&nbsp; &#91;\u2026]\n<\/code><\/pre>\n\n\n\n<p>And with that command, my Symfony application is up and running at <code>http:\/\/localhost:8080\/<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/s3.eu-central-1.amazonaws.com\/files.strehle.de\/tim\/blog\/2019-06-10-symfony-app.png\"><img decoding=\"async\" src=\"https:\/\/s3.eu-central-1.amazonaws.com\/files.strehle.de\/tim\/blog\/2019-06-10-symfony-app.png\" alt=\"PhpStorm IDE screenshot\"\/><\/a><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>I can add more required packages via Composer (I usually add <a href=\"https:\/\/github.com\/Seldaek\/monolog\/blob\/master\/README.md\">Monolog<\/a>):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker exec --workdir \/opt\/app --user daemon:daemon -it 9fade34cfdb7 \\\n  composer require symfony\/monolog-bundle\n<\/code><\/pre>\n\n\n\n<p>And use the Symfony <code>console<\/code> command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker exec --workdir \/opt\/app --user daemon:daemon -it 9fade34cfdb7 \\\n  bin\/console about\n -------------------- --------------------------------- \n  Symfony                                               \n -------------------- --------------------------------- \n  Version              4.3.1                            \n  &#91;\u2026]\n -------------------- --------------------------------- \n  PHP                                                   \n -------------------- --------------------------------- \n  Version              7.3.5                            \n  &#91;\u2026]\n -------------------- --------------------------------- \n<\/code><\/pre>\n\n\n\n<p>To see Symfony log output on the Docker console output (STDOUT), I change <a href=\"https:\/\/github.com\/tistre\/symfony-in-docker-example\/blob\/master\/app\/config\/packages\/dev\/monolog.yaml\"><code>app\/config\/packages\/dev\/monolog.yaml<\/code><\/a> to contain <code>path: \"php:\/\/stdout\"<\/code>.<\/p>\n\n\n\n<p>The next step is <a href=\"https:\/\/symfony.com\/doc\/current\/page_creation.html\">creating your first page in Symfony<\/a>!<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Thanks<\/h3>\n\n\n\n<p>Thanks to the bloggers that helped me figure all this out \u2013 <a href=\"http:\/\/geekyplatypus.com\/making-your-dockerised-php-application-even-better\/\">Making your dockerised PHP application even better<\/a> by the Geeky Platypus, <a href=\"https:\/\/bitpress.io\/simple-approach-using-docker-with-php\/\">My Simple Approach to using Docker and PHP<\/a> by Paul Redmond, and <a href=\"https:\/\/www.pascallandau.com\/blog\/structuring-the-docker-setup-for-php-projects\/\">Structuring the Docker setup for PHP Projects<\/a> by Pascal Landau.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I\u2019m currently learning Docker, and finding out how to develop a (new) Symfony 4 (PHP 7) application locally on my Mac and run it within a Docker container, instead of hosting it in a Linux VM in VMware Fusion as I used to do. Docker Desktop and the PhpStorm IDE are already installed. Docker setup [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_share_on_mastodon":"0"},"categories":[1],"tags":[],"class_list":["post-1883","post","type-post","status-publish","format-standard","hentry","category-weblog"],"share_on_mastodon":{"url":"","error":""},"_links":{"self":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/1883","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/comments?post=1883"}],"version-history":[{"count":1,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/1883\/revisions"}],"predecessor-version":[{"id":1891,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/1883\/revisions\/1891"}],"wp:attachment":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/media?parent=1883"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/categories?post=1883"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/tags?post=1883"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}