Under the hood Deploying a Symfony app in a Docker container with Render

Part of the "Under the hood" series of posts, detailing how stackselect.tech is built, and giving you a better idea how some tools listed on stackselect.tech work.

Firstly, why Render? For stackselect.tech, I was looking for a low-maintenance and affordable hosting solution. I'm okay with setting up servers, maintaining infrastructure-as-code playbooks with ansible but since this is a side-project, I was looking for something capable of taking away all that effort from me.

Render hits that sweet-spot for now where I can create a simple Docker image and deploy changes with a simple git push. Render builds the docker image and deploys the site with zero downtime. I don't have to log in to any servers. Ever.

Render is a unified platform to build and run all your apps and websites with free SSL, a global CDN, private networking, and auto deploys from GitHub and GitLab.

The docker image

At the time of writing, this is the Dockerfile that powers stackselect.tech.

FROM php:7.4-fpm-alpine as composer_deps
COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer
COPY src/Kernel.php /app/src/Kernel.php
COPY composer.json /app/
COPY composer.lock /app/
COPY symfony.lock /app/
RUN composer install --no-dev --optimize-autoloader --no-interaction --no-scripts

FROM node:12.16-alpine3.11 as node_deps
COPY package.json /app/package.json
COPY yarn.lock /app/yarn.lock
RUN yarn

FROM node_deps as frontend_build
COPY webpack.config.js /app/webpack.config.js
COPY postcss.config.js /app/postcss.config.js
COPY assets /app/assets
RUN yarn encore production

FROM php:7.4-fpm-alpine
    apk add --no-cache \
        nginx=1.16.1-r6 \
        bash \
    && \
    wget https://raw.githubusercontent.com/chrismytton/shoreman/380e745d1c2cd7bc163a1485ee57b20c76395198/shoreman.sh && chmod +x shoreman.sh && mv shoreman.sh /usr/local/bin/shoreman
COPY --from=composer_deps /app/vendor /app/vendor
COPY --from=frontend_build /app/public/build /app/public/build
COPY . /app
RUN cp /app/infrastructure/php-fpm/php-fpm.conf /usr/local/etc/php-fpm.conf && \
    cp /app/infrastructure/php-fpm/www.conf     /usr/local/etc/php-fpm.d/www.conf && \
    cp /app/infrastructure/nginx/nginx.conf     /etc/nginx/nginx.conf && \
    cp /app/infrastructure/nginx/vhost.conf     /etc/nginx/conf.d/default.conf

RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log

COPY Procfile /app/Procfile
RUN addgroup app && adduser -D -G app app && \
    chown -R app:app \
        /app \
        /var/lib/nginx/ \
USER app
CMD ["/usr/local/bin/shoreman"]

As you can see, it's a Symfony app, so the first stage of this multistage dockerfile is to install the composer dependencies.

The next step is some JS dependencies. We use Symfony's Encore to build the limited Javascript in the project, along with TailwindCSS and VueJS. We install these dependencies in one step, and build the production assets in another.

Finally, we create the stage which will ultimately serve our traffic. It's based off the official PHP docker image, to which we add nginx, configure various settings (saved in config files like nginx.conf). We copy in shoreman since, in this image, we're running more than one process.

There's arguments that running multiple processes in this way is not ideal but in this scenario it allows us to have a very simple setup with the fewest moving parts.

Deploying this docker image to Render's container-based PaaS

To replicate our setup, after creating an account with Render, you need to select "New Web Service". Connect your repository and configure some production settings. Make sure to select the "Docker" service type and add any production environment variables you need.

If they're secrets, consider using Symfony's secret encryption method to prevent accidentally exposing any credentials.

You'll see logs as Render tries to build and deploy your image.

May 3 02:36:47 PM  ==> Cloning from https://github.com/vendor/project...
May 3 02:36:53 PM  ==> Checking out commit a4da30b4ad18645fd068e10ae0f17f4a1cb8e719 in branch master
May 3 02:36:56 PM  INFO[0000] Downloading base image composer:1.10
May 3 02:37:05 PM  INFO[0008] Downloading base image php:7.4-fpm-alpine
May 3 02:38:59 PM   ______________________________
May 3 02:38:59 PM  < Pushing image to registry... >
May 3 02:38:59 PM   ------------------------------
May 3 02:38:59 PM          \   ^__^
May 3 02:38:59 PM           \  (oo)\_______
May 3 02:38:59 PM              (__)\       )\/\
May 3 02:38:59 PM                  ||----w |
May 3 02:38:59 PM                  ||     ||
May 3 02:39:05 PM   ______
May 3 02:39:05 PM  < Done >
May 3 02:39:05 PM   ------
May 3 02:39:05 PM          \   ^__^
May 3 02:39:05 PM           \  (oo)\_______
May 3 02:39:05 PM              (__)\       )\/\
May 3 02:39:05 PM                  ||----w |
May 3 02:39:05 PM                  ||     ||

Additionally, you can go further and add Docker healthchecks to help Render restart your services should something go wrong. You can also set up databases as private-services, but for now, for stackselect, everything runs off an SQLite database that's actually committed to the project along with the rest of the code.

Since your app is available on a subdomain, like your-project.onrender.com you'll probably also want to set up a custom domain. The setup is a CNAME record pointing your domain to their servers, and the instructions are all fairly straightforward.

One final benefit is the pull request environments. If I want to test a change, I can create a pull request in my repository on Github. Render automatically detects that pull request and creates an environment with the new changes, pings you a link as a github comment and throws away the environment when it's merged or closed, so there's no need for a staging server at all.

Overall, Render seems like a good option, especially if your project is not too complex. And for $7 a month to try it out, you can try it for yourself in a few hours one evening.

Render is a unified platform to build and run all your apps and websites with free SSL, a global CDN, private networking, and auto deploys from GitHub and GitLab.

Add your perspective

Spotted a mistake? Got something to add?

Please be friendly and respectful to others!

Our analytics are public, provided by Simple Analytics.

stackselect.tech is alpha

Help shape the app and follow dev updates on IndieHackers or follow @stackselect on twitter for updates on new tools

Follow on IndieHackers → Follow on Twitter →