{"id":1780,"date":"2025-12-20T16:01:00","date_gmt":"2025-12-20T16:01:00","guid":{"rendered":"https:\/\/editjournal.redakt.eu\/faxmodem\/?p=1780"},"modified":"2025-12-20T19:48:15","modified_gmt":"2025-12-20T19:48:15","slug":"deploy-render-com-docker","status":"publish","type":"post","link":"https:\/\/editjournal.redakt.eu\/faxmodem\/blog\/development\/cakephp\/deploy-render-com-docker\/","title":{"rendered":"Deploying a simple CakePHP app to Render.com using Docker"},"content":{"rendered":"<p>Render doesn\u2019t have a \u201cnative PHP runtime\u201d like it does for Node\/Python, but it does have Docker. So we can use a Docker image that will set up the PHP runtime for us.<\/p>\n<p>To make it happen, we will have to add these two files to our application:<\/p>\n<ul>\n<li><code>Dockerfile<\/code> that imports and uses a prebuilt image from <a href=\"https:\/\/github.com\/richarvey\/nginx-php-fpm\">richarvey\/nginx-php-fpm<\/a><\/li>\n<li>Nginx configuration, for URL rewrites among other things<\/li>\n<\/ul>\n<h3>Dockerfile<\/h3>\n<p>Add a file with the name <code>Dockerfile<\/code> and below contents to your application root.<\/p>\n<pre><code>FROM richarvey\/nginx-php-fpm:latest\n\nWORKDIR \/var\/www\/html\n\n# Copy composer.json first to have it on a separate cache layer\nCOPY composer.json .\/\nRUN composer install --no-scripts --no-interaction --optimize-autoloader\n\n# Copy the rest of the application\nCOPY . .\n\n# Ensures writable folders and config\/app_local.php before continuing\nRUN composer run-script post-install-cmd --no-interaction\n\n# Run the post-update scripts\nRUN composer run-script post-update-cmd --no-interaction\n\n# Environment variables for start.sh below\nENV WEBROOT \/var\/www\/html\/webroot\nENV REAL_IP_HEADER 1\nENV RUN_SCRIPTS 0\nENV PHP_ERRORS_STDERR 1\n# Tell start.sh to skip composer because we handle it ourselves\nENV SKIP_COMPOSER 1\n\n# Source: github.com\/richarvey\/nginx-php-fpm\/blob\/main\/scripts\/start.sh\n# To debug: CMD [&quot;\/bin\/bash&quot;, &quot;-x&quot;, &quot;\/start.sh&quot;]\nCMD [&quot;\/start.sh&quot;]<\/code><\/pre>\n<p>We start by copying only <code>composer.json<\/code> and running composer install right away. This allows Docker to cache the composer layer, and only reinstall the dependencies when they actually change.<\/p>\n<p>We then copy everything else and run <code>post-install-cmd<\/code> and <code>post-update-cmd<\/code> hooks in a specific sequence - more on that below.<\/p>\n<p>Next is the main script <code>start.sh<\/code>, provided by the <code>richarvey\/nginx-php-fpm<\/code> image. We set up the environment variables it expects and finally run it. To see all possible environment variables and better understand what <code>start.sh<\/code> does, see <a href=\"https:\/\/github.com\/richarvey\/nginx-php-fpm\/blob\/main\/scripts\/start.sh\">its source code<\/a>.<\/p>\n<p><strong>Important: On composer scripts sequence<\/strong><\/p>\n<p>We have to specifically ensure the order in which the composer scripts are executed. By default, it would attempt to run <code>post-update-cmd<\/code> without (or in parallel to)  <code>post-install-cmd<\/code>. The issue is, <code>post-install-cmd<\/code> <strong>must<\/strong> come first and set the security salt and filesystem permissions, else we end up with the following errors:<\/p>\n<pre><code>[TypeError] Cake\\Utility\\Security::setSalt(): Argument #1 ($salt) must be of type string, null given, called in \/var\/www\/html\/config\/bootstrap.php on line 188 in \/var\/www\/html\/vendor\/cakephp\/cakephp\/src\/Utility\/Security.php on line 304\nScript bin\/cake plugin assets symlink handling the post-update-cmd event returned with error code 1\n\n...\n\nWARNING: [pool www] child 326 said into stderr: &quot;NOTICE: PHP message: PHP Warning:  file_put_contents(\/var\/www\/html\/logs\/error.log): Failed to open stream: Permission denied in \/var\/www\/html\/vendor\/cakephp\/cakephp\/src\/Log\/Engine\/FileLog.php on line 137&quot;\n\n... and so on<\/code><\/pre>\n<h3>Nginx config<\/h3>\n<p>The <code>start.sh<\/code> will check whether <code>conf\/nginx\/nginx-site.conf<\/code> exists and if so, use it as the main Nginx config, which is what we need.<\/p>\n<p>The below configuration, among other things, ensures URL rewriting is enabled and points to <code>index.php<\/code> - so that CakePHP routing works.<\/p>\n<pre><code># A part of richarvey\/nginx-php-fpm setup\n# start.sh expects Nginx configuration at conf\/nginx\/nginx-site.conf\nserver {\n    listen 80;\n    server_name _;\n    sendfile off;\n    charset utf-8;\n\n    root \/var\/www\/html\/webroot;\n    index index.html index.htm index.php;\n\n    access_log \/dev\/stdout;\n    error_log \/dev\/stdout info;\n    error_page 404 \/index.php;\n\n    add_header X-Frame-Options &quot;SAMEORIGIN&quot;;\n    add_header X-XSS-Protection &quot;1; mode=block&quot;;\n    add_header X-Content-Type-Options &quot;nosniff&quot;;\n\n    location \/ {\n        try_files $uri $uri\/ \/index.php?$args;\n    }\n\n    location = \/favicon.ico {\n        access_log off;\n        log_not_found off;\n    }\n\n    location = \/robots.txt {\n        access_log off;\n        log_not_found off;\n    }\n\n    location ~* \\.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ {\n        expires 5d;\n    }\n\n    location ~ \\.php$ {\n        fastcgi_split_path_info ^(.+\\.php)(\/.+)$;\n        fastcgi_pass unix:\/var\/run\/php-fpm.sock;\n        fastcgi_index index.php;\n        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n        fastcgi_param SCRIPT_NAME $fastcgi_script_name;\n        include fastcgi_params;\n    }\n\n    # Allow .well-known\n    location ^~ \/.well-known\/ {\n        allow all;\n    }\n\n    # Deny all other dotfiles\n    location ~ \/\\.(?!well-known\/) {\n        deny all;\n        log_not_found off;\n    }\n}<\/code><\/pre>\n<h3>Resources and further reading<\/h3>\n<ul>\n<li><a href=\"https:\/\/render.com\/docs\/deploy-php-laravel-docker\">https:\/\/render.com\/docs\/deploy-php-laravel-docker<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/render-examples\/php-laravel-docker\/commit\/c4bc0ccc7b72554135fed30eb676c37e86a40e87\">https:\/\/github.com\/render-examples\/php-laravel-docker\/commit\/c4bc0ccc7b72554135fed30eb676c37e86a40e87<\/a><\/li>\n<li><a href=\"https:\/\/book.cakephp.org\/5\/en\/installation.html#nginx\">https:\/\/book.cakephp.org\/5\/en\/installation.html#nginx<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/cakephp\/app\/pull\/943\/files\">https:\/\/github.com\/cakephp\/app\/pull\/943\/files<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Render doesn\u2019t have a \u201cnative PHP runtime\u201d like it does for Node\/Python, but it does have Docker. So we can use a Docker image that will set up the PHP runtime for us. To make it happen, we will have to add these two files to our application: Dockerfile that imports and uses a prebuilt&hellip; <a class=\"more-link\" href=\"https:\/\/editjournal.redakt.eu\/faxmodem\/blog\/development\/cakephp\/deploy-render-com-docker\/\">Continue reading <span class=\"screen-reader-text\">Deploying a simple CakePHP app to Render.com using Docker<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1047],"tags":[715,1269,1252],"class_list":["post-1780","post","type-post","status-publish","format-standard","hentry","category-cakephp","tag-cakephp","tag-docker","tag-render-com","entry"],"_links":{"self":[{"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/posts\/1780","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/comments?post=1780"}],"version-history":[{"count":5,"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/posts\/1780\/revisions"}],"predecessor-version":[{"id":1785,"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/posts\/1780\/revisions\/1785"}],"wp:attachment":[{"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/media?parent=1780"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/categories?post=1780"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/editjournal.redakt.eu\/faxmodem\/wp-json\/wp\/v2\/tags?post=1780"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}