Deployment

Introduction

This is usually the last step that I take when launching an app from the ground up. We will break deployment down into five steps. The motif of this project is to self-host, so you won't see a Vercel deployment step here anywhere in the meantime. But if anyone wants to deploy this project using Vercel message me if you need help.

There are a lot of ways to self-host a NextJS app. You can do it by setting up SSL Certs + A process manager for Node like PM2 + Reverse Proxy like Nginx. But I want to try Coolify for engineering's sake, and all of the things that I mentioned are set up using a single command, except for PM2 because Coolify uses Docker to host your apps, and this way I can deploy any app written in any programming language.

Steps overview:

  1. Obtaining a Domain Name

  2. Setting up your VPS with Coolify

  3. Setting up your Github Container Registry details

  4. Setting up the deployment pipeline (Continuous Delivery)

  5. Setting up your Coolify App

Obtaining a Domain Name

If you want to launch an app, a domain name is one of the key things that you should get. Cloudflare is my go-to domain name registrar because it doesn't only provide the registration but it also provides DDoS protection, tunneling (we'll discuss this later on), S3 bucket alternative, etc. The list goes on. If you want to go for another provider it's your choice, but I'll discuss Cloudflare here.

  1. Sign up for a Cloudflare account

  2. Go to this page called Register Domains

  1. Search for the domain name that you would like to purchase then click the purchase button once it pops up. You should be able to see this screen below, input your contact details, and card details, and then click complete purchase.

  1. Your domain should be able to show up here after a while

  1. That's it your domain name is ready to use. We will use that later on.

Setting up your VPS with Coolify

For the VPS it depends on you on where you will buy it, and the location where you would like it to be placed. As much as possible place it near to your database location, so latency can be minimized. I tried DigitalOcean, Hetzner, OVH, and Vultr. Right now, I am using OVH to host this project because it's cheap, for 3 USD you can have a 2 Core and 2GB RAM machine. I would suggest Hetzner and OVH if we're talking about pricing here. But regardless of where you buy it, choose Ubuntu the latest one as your operating system. Once you have purchased a VPS, proceed with the steps below.

  1. Run this command to install Coolify. Coolify will help us automate deployments and manage our apps.

curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

  1. Once installed, it should be able to give you the location of its dashboard. e.g 192.168.1.1:8000

  2. Create your Coolify account and proceed with the onboarding. Choose localhost on the onboarding step, because we will both host Coolify and the apps inside the same VPS.

  3. Once you're done with the onboarding, click the Projects tab and create a new project.

  1. Click "Add New Resource"

  1. Click "Existing Docker Image". Our NextJS app shall be Docker-based and will be built inside a GitHub Action instance, which you will see later on.

  1. Select "localhost" for the server and you will see this screen that prompts you to input a docker image. Our docker images shall be stored in GHCR (Github Container Registry), if you want you can use Dockerhub but I don't see the point of splitting out the GitHub Action and the Container Image registry since they work hand in hand. As you can see below the format is : <registry_name>/<your_github_username>/<app_image_name>:<version>

Just replace erehwonmi with your GitHub username, then click save

  1. This page should appear once you click save, for now, leave it as it is.

Setting up Github Container Registry details

  1. Obviously, you need a GitHub account.

  2. Fork / Clone the TheNextStartup repository to your GitHub account.

  3. Create a GitHub Personal Access Token. You will need this to authenticate with GHCR. Go to Settings > Developer Settings > Tokens (classic) then Generate new token (classic)

  1. Fill up token details as shown below then generate the token.

  1. SSH to your VPS, then login to your registry by typing this command. That command should allow Coolify to pull up any container image from your registry so that it can spin up the app.

docker login ghcr.io -u <username> -p <your_generated_token>

Setting up the deployment pipeline (Continuous Delivery)

In this section, I'll explain what's happening on the deployment script via code comments. The continuous delivery script should help you push changes automatically to your application on the server. Both the web app and the admin app contain the same configuration of the CD pipeline.

// Some codename: Build Web App
on:
  push:
    // This will trigger on the main branch push only
    branches: ["main"]
    // Once there are changes to these directories, this jobs on this script will run.
    paths:
        - '.github/workflows/deploy-web-app.yml'
        - 'apps/web-app/**'
        - 'db/**/*.*'
        - 'Dockerfile.webapp'
        - package.json
  workflow_dispatch: // This is to enable to run this workflow manually via GitHub UI

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: "erehwonmi/thenextstartup-web" // Replace erehwonmi with your GitHub username

jobs:
  generate:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js 20.0
        uses: actions/setup-node@v3
        with:
          node-version: 20.0
          cache: 'npm'
      - name: NPM Install
        run: npm install
      - name: NPM Install Drizzle-kit
        run: npm install drizzle-kit
      - name: Generate ENV File for DB
        // Store your environment variables here
        https://github.com/<your_github_username>/thenextstartup/settings/secrets/actions
        run: |
          cat <<-EOF >> .env
          DATABASE_URL=${{ secrets.DATABASE_URL }}
          DB_AUTH_TOKEN=${{ secrets.DB_AUTH_TOKEN }}
          EOF
      - name: Generate Drizzle Client
        run: npm run db:generate
      - name: Push Drizzle Turso Schema
        run: npm run db:push

  deploy:
    runs-on: ubuntu-latest
    needs: generate
    steps:
      - name: "Clone repo"
        uses: actions/checkout@v3
      - name: Dynamically creating .env file
        run: |
          cat <<-EOF >> .env
          NODE_ENV=PRODUCTION
          ENV=PRODUCTION
          // Store your environment variables here
          https://github.com/<your_github_username>/thenextstartup/settings/secrets/actions
          AXIOM_TOKEN=${{ secrets.AXIOM_TOKEN }}
          AXIOM_DATASET=${{ secrets.AXIOM_DATASET }}
          DATABASE_URL=${{ secrets.DATABASE_URL }}
          DB_AUTH_TOKEN=${{ secrets.DB_AUTH_TOKEN }}
          GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}
          GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}
          HOST_NAME=${{ secrets.HOST_NAME }} // The domain name for your app
          STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }}
          STRIPE_WEBHOOK_SECRET_KEY=${{ secrets.STRIPE_WEBHOOK_SECRET_KEY }}
          RESEND_API_KEY=${{ secrets.RESEND_API_KEY }}
          LEMONSQUEEZY_SECRET_KEY=${{ secrets.LEMONSQUEEZY_SECRET_KEY }}
          LEMONSQUEEZY_STORE_ID=${{ secrets.LEMONSQUEEZY_STORE_ID }}
          LEMONSQUEEZY_API_KEY=${{ secrets.LEMONSQUEEZY_API_KEY }}
          EOF
      - name: Login to ghcr.io
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GHCR_TOKEN  }}
      - name: Build image and push to registry
        uses: docker/build-push-action@v4
        with:
          context: .
          file: Dockerfile.webapp
          platforms: linux/amd64
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
      - name: Deploy to Coolify
        run: | 
         curl --request GET '${{ secrets.COOLIFY_WEBHOOK }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'

Each app has a unique COOLIFY_WEBHOOK in order to notify Coolify that it needs to pull the latest image from the registry and redeploy our app. You can obtain this webhook URL by going back to the Coolify app that you made in the previous step. Copy the URL that is inside that red circle and store it inside your repository's GitHub secrets.

For the COOLIFY_TOKEN. Go to Keys & Tokens > API Tokens and create a new token with the following configuration. Copy that key and store it inside your repository's GitHub secrets.

For the Dockerfile the TLDR is it has a multi-stage build that enables us to build and containerize our NextJS app.

Setting up your Coolify App

In this step, we will establish a tunnel from Cloudflare to your VPS. In this way, only Cloudflare can talk with your VPS and you can serve your apps to the internet without exposing any ports (except SSH port 22 you need to expose this). You can skip this part if you don't want to do this. You can set up an A Record for your app.

  1. Go to Cloudflare Account > Zero Trust > Network > Tunnels > Create a Tunnel > Select Cloudflared and just complete the steps

  1. At some point, you will stumble upon this, select Debian copy that command, and the SSH to your VPS paste it then run it.

  1. After setting that up, your Tunnel should appear healthy

  2. Click your Tunnel name > Click Edit > Add a public hostname then mimic details below. Your purchased domain name should pop up. Cloudflare talks to your app internally via HTTP localhost. The generated public hostname should be able to point to your web app and expose it to the internet.

  1. Copy the generated public hostname to your Coolify app to the Domains input box, then click Save. The button right beside General.

  1. Go back to your cloned TheNextStartup repository, then go to the Actions tab and manually trigger the workflow via Run workflow. This will run the build step and deploy your app to Coolify.

  1. You can also set a public hostname for your Coolify app instance by doing the previous step and pointing it here (Instance's Domain) then click Save. If Saving doesn't work, you have to restart your VPS.

  1. After all of those steps, your app is finally live. Moving forward if you need to create another app the TLDR is just to Create a Coolify App > Create Cloudflare Public Hostname > Point public hostname to Coolify App > Manually Trigger Github Action > App is now live.

  2. Once your app is live don't forget to set up your payment provider's (Stripe / Lemonsqueezy) webhook API endpoint and secret environment variables.

Last updated