Building A Slack Clone With Preview Environments
Tags: nextjs, opensource, devops, docker
Published: 2023-05-17 10:54:00

## 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](https://github.com/livecycle/supabase-nexjs-preevy-example)).

To do this we’ll be using a combination of [supabase](https://supabase.com/) + [Next.js](https://nextjs.org/). We’ll also be using a tool called [Preevy](https://github.com/livecycle/preevy) to provision the preview environments for our project.

## Getting Started

On the backend side of things, we’re going to use [Supabase](https://supabase.com/), an OSS project designed as an alternative to [Google’s Firebase](https://firebase.google.com/) - a platform for DB, realtime communication, authentication and more.

On the frontend side of things, we’ll be using a simple [Next.js](https://nextjs.org/) app - a [Slack](https://slack.com/) clone, taken from one of Supabase’s examples - [here’s a link to the repo](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone) 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](https://github.com/livecycle/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](https://aws.amazon.com/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](https://github.com/livecycle/preevy). Can you help me out by starring the repository? It helps more developers discover [Preevy](https://github.com/livecycle/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](https://supabase.com/docs/guides/getting-started/local-development), we can leverage it to completely build a [self-contained environment](https://github.com/Yshayy/self-contained-repositories) - 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](https://github.com/supabase/supabase/blob/master/docker/docker-compose.yml). 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
```docker
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"]
```

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](https://github.com/supabase/supabase/blob/master/examples/slack-clone/nextjs-slack-clone/full-schema.sql).

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:

```yaml
  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](https://github.com/supabase/supabase/blob/master/docker/.env.example), `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](https://preevy.dev/recipes/service-discovery). 

We can make our environment variables support both the Preevy URLs and local development by leveraging bash's [parameter substitution](https://tldp.org/LDP/abs/html/parameter-substitution.html).

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](https://github.com/livecycle/supabase-nexjs-preevy-example/blob/master/docker/.env.example)

### 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](https://preevy.dev/#getting-started).

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](https://github.com/marketplace/actions/preevy-up), as described in its documentation, here is a usage example:

```yaml
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](https://supabase.com/), [Next.js](https://nextjs.org/) and [Preevy](https://github.com/livecycle/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](https://docs.docker.com/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.