DEV Community

Cover image for Building A Slack Clone With Preview Environments
AssafKr for Livecycle

Posted on • Updated on

Building A Slack Clone With Preview Environments

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 --from=deps /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 --from=builder /app/public ./public

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 1988

ENV PORT 1988

CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

We'll define

SITE_URL=${PREEVY_BASE_URI_STUDIO_3000:-http://localhost:3000}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Clone this repo https://github.com/livecycle/supabase-nexjs-preevy-example
  2. Inside the docker directory, run cp .env.example .env
  3. Inside the docker directory, run preevy 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 }}           
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (4)

Collapse
 
mindplay profile image
Rasmus Schultz

Why a dedicated deployment tool for previews only? Doesn't that just mean I'll have to solve the deployment problem twice? Don't most deployment tools already support multiple environments? Wouldn't I want production and preview environments, as well as the deployment procedure, to be as similar to production as possible - so I can be confident the production deployments will work?

Collapse
 
assafkr profile image
AssafKr

@mindplay This is a great question!

While, in a perfect world, we'd like every environment to be as close to production as possible, in the real world it's very hard to achieve. Production/staging environments many times rely on third party services, complex authn/authz implementations, SSL certs, dealing with data and PII and all that jazz. That is why local dev environments are simpler and lighter.

The purpose of a preview environment is similar to this of a dev environment: to have a quick feedback loop and see how the code you write translate into an application. In most cases, this fidelity of dev env is enough for previews and allows for quick build and deploy times, and saves costs.

Additionally, the configurations of preview envs are much simpler, just think about a docker-compose config vs. k8s

Collapse
 
mindplay profile image
Rasmus Schultz • Edited

But you need production deployments. Those aren't really optional. So you're not getting around the work of configuring more complex tools. And as long as you're setting those up anyway, adding another tool with a completely different configuration, maintaining any overlapping configuration in two different forms, the additional learning curve - no matter how simple this tool may be, I'm not really convinced that's going to be less complexity overall.

But I'm not a Dev Ops person, so who knows. Maybe these other tools are so unbearably complex it's actually worth adding an entirely different deployment strategy just to avoid the extra work with them. (If so, it kind of seems like maybe that's the problem that really needs solving though.)

Collapse
 
zevireinitz profile image
Zevi Reinitz

Great writeup. Thanks!