TL;DR
In this tutorial, we’re going to build a full-stack Slack-like application, with multiple running services. We’ll also set up preview environments for every commit in every branch, complete with all the running services. (spoiler alert - if you prefer to look straight at the code you can check out the finished product is in this repo).
To do this we’ll be using a combination of supabase + Next.js. We’ll also be using a tool called Preevy to provision the preview environments for our project.
Getting Started
On the backend side of things, we’re going to use Supabase, an OSS project designed as an alternative to Google’s Firebase - a platform for DB, realtime communication, authentication and more.
On the frontend side of things, we’ll be using a simple Next.js app - a Slack clone, taken from one of Supabase’s examples - here’s a link to the repo that we’ll be using.
While this application has many backend services and a frontend server, we’re going to show how to easily set up preview environment, automatically built and deployed on every commit in every branch.
To achieve that, we’ll be using Preevy, an open source tool for provisioning preview environments with minimal configuration. Preevy will build and deploy our app using a cloud provider, in this case cheap AWS Lightsail VMs, while exposing the services with a public, sharable URL for team collaboration.
A quick request 🤗
I’m trying to hit our first 1K stars for Preevy. Can you help me out by starring the repository? It helps more developers discover Preevy and helps us to keep creating interesting content for the community. Thanks!
Why this is interesting
Preview environments are a game-changer in fast-paced development workflows. They allow teams to review actual running versions of the application in review time, before deployment to staging or production.
Preview environments (also known as “deploy previews” or “ephemeral environments”) enable every stakeholder on the team to view and collaborate on a code change at PR time, which makes for faster product development.
Since supabase allows local development, we can leverage it to completely build a self-contained environment - meaning we can easily build the application with all of its dependencies based on the source code alone. No external service is required to make the application work.
So this project is a great example of building a multi-service application with some great open source tools and experience how preview environments can impact your workflow and your overall developer experience.
How to build it
1. Create the docker-compose.yaml
First, we’re going to create a docker-compose.yaml
file with all the supabase services. We are basing our compose file out of supabase’s own compose file. Along with the docker-compose.yaml
itself, we need some configurations and SQL files to initialize the services and DBs. Basically they are copied from supabase’s repo, so we’re not going to talk about them.
2. Create a Dockerfile
for the Next.js application
We need to add the Slack clone application to the compose file. To do that, we need to create a Docker file for it.
Here are its contents
FROM node:18-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY yarn.lock ./
RUN yarn --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_SUPABASE_URL
ARG NEXT_PUBLIC_SUPABASE_KEY
RUN yarn build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY /app/public ./public
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
USER nextjs
EXPOSE 1988
ENV PORT 1988
CMD ["node", "server.js"]
Notice we are passing the NEXT_PUBLIC_SUPABASE_URL
and NEXT_PUBLIC_SUPABASE_KEY
args so they are used as environment variables in the build of the app, which allows to connect to the supabase services from our frontend app.
3. Add the application’s schema to the compose service volume
The Slack application expects a certain schema in its database, like the channel and messages tables. We need to load the schema to the database when we boot it up. Here is the schema SQL file in the example repo.
To do that, we are adding the full-schema.sql
file to the ./docker/volumes/db
folder and adding it the the composes’s db service volumes like so:
db:
# Removed the rest of the configurations for clarity
volumes:
# add the following line after the rest of the volumes:
- ./volumes/db/full-schema.sql:/docker-entrypoint-initdb.d/init-scripts/100-full-schema.sql:Z
4. Inject the preview environment services’ urls to the environment variables
In order for the services to communicate with each other, they need the addresses of one another. In the original ./docker/.env.example
file from the supabase repo, localhost
URLs are used to connect to the different services, since it is assumed the environment will run on the developer’s private machine. In our case, the environment is going to run on the preview environment and have public internet URLs.
To get the URLs, Preevy exposes the public facing URIs using special build time environment variables, as explained in the docs.
We can make our environment variables support both the Preevy URLs and local development by leveraging bash’s parameter substitution.
For example, instead of defining the SITE_URL
variable with the local URL
SITE_URL=http://localhost:3000
We’ll define
SITE_URL=${PREEVY_BASE_URI_STUDIO_3000:-http://localhost:3000}
Which we’ll set the value of http://localhost:3000
if the value of PREEVY_BASE_URI_STUDIO_3000
is null, meaning we run outside of Preevy. You can look at the modified ./docker/.env.example
file in the repo of this example - over here
5. Using Preevy to deploy the environment on your local machine
To create a preview environment from your local machine keep reading this section. You can also skip that and configure it directly to run on your CI, as described in the next section.
First step is to make sure Preevy
is installed and configured, as detailed in the documentation.
Once that’s done, do the following:
- Clone this repo https://github.com/livecycle/supabase-nexjs-preevy-example
- Inside the
docker
directory, runcp .env.example .env
- Inside the
docker
directory, runpreevy up
And that’s it!
6. Creating a GH action workflow automatically deploys on every update
Preevy has a convenient GitHub action - livecycle/preevy-up-action, as described in its documentation, here is a usage example:
name: Deploy Preevy environment
on:
pull_request:
types:
- opened
- synchronize
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::12345678:role/my-role
aws-region: eu-west-1
- uses: actions/checkout@v3
- uses: livecycle/preevy-up-action@latest
id: preevy
with:
profile-url: "s3://preevy-12345678-my-profile?region=eu-west-1"
docker-compose-yaml-path: "./docker/docker-compose.yaml"
- uses: mshick/add-pr-comment@v2
with:
message: ${{ steps.preevy.outputs.urls-markdown }}
Summary
In summary, using open source tools and frameworks like supabase, Next.js and Preevy can be a powerful combination for creating scalable full-stack applications, and more effective development workflows.
By following the provided steps and utilizing Docker Compose, developers can easily set up preview environments that automatically build and deploy with each commit, facilitating efficient code review and collaboration.
With preview environments, stakeholders can review running versions of the application in real time, and developers can benefit from clearer feedback, faster development cycles and a much better developer experience.