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
WORKDIR /app
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
WORKDIR /app
COPY package.json /app/package.json
COPY yarn.lock /app/yarn.lock
RUN yarn
FROM node_deps as frontend_build
WORKDIR /app
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
WORKDIR /app
RUN \
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
EXPOSE 8080
RUN addgroup app && adduser -D -G app app && \
chown -R app:app \
/app \
/var/lib/nginx/ \
/etc/nginx
USER app
ENV APP_ENV prod
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.