Skip to content

Deployment Guide

This guide describes how SILAB Digital Hub is deployed today from this repository.

Overview

  • Frontend: Next.js app in next-fe, exposed from the container on port 3000
  • Backend: Express and Prisma API in kayanou-service, exposed from the container on port 8080
  • Database:
  • Local development uses the postgres container from docker-compose.yml
  • Staging and production use an external PostgreSQL database through DATABASE_URL
  • Reverse proxy: host-level NGINX proxies public traffic to 127.0.0.1:3000 and 127.0.0.1:8080
  • Automation: GitHub Actions runs on a self-hosted runner, then SSHes into the VM and executes ./deploy.sh

Environments

Environment Branch Public frontend Public backend Deployment path
Local any http://localhost:3000 http://localhost:8080 Manual with ./dev-start.sh
Staging dev https://stage.silab.website https://stage.api.silab.website GitHub Actions stage.yml or manual ./deploy.sh
Production main https://silab.website https://api.silab.website GitHub Actions prod.yml or manual ./deploy.sh

Prerequisites

VM requirements

  • Ubuntu 20.04+ or similar Linux distribution
  • Docker Engine
  • Docker Compose plugin
  • Git
  • NGINX
  • Access to create /opt/kayanou/.env

CI/CD requirements

  • A GitHub self-hosted runner registered for this repository
  • SSH access from the runner to the target VM
  • GitHub repository secrets:
  • SSH_KEY
  • SSH_HOST for staging
  • SSH_HOST_PROD for production

Step 1: Prepare the VM

1.1 Install Docker and system packages

sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo apt install -y docker-compose-plugin git nginx
sudo mkdir -p /opt/kayanou
sudo chown "$USER":"$USER" /opt/kayanou

1.2 Open only the ports you intend to expose

docker-compose.prod.yml publishes ports 3000 and 8080 on the host. If NGINX is the public entrypoint, restrict those ports with the firewall.

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 3000/tcp
sudo ufw deny 8080/tcp
sudo ufw enable
sudo ufw status

Step 2: Clone the repository

The deployment workflows expect the repository to live at ~/W26project-KAYANOU.

cd ~
git clone https://github.com/UAlberta-CMPUT401/W26project-KAYANOU.git
cd W26project-KAYANOU

For manual staging deployment:

git checkout dev
git pull origin dev

For manual production deployment:

git checkout main
git pull origin main

Step 3: Create the server environment file

Production deploys do not read .env.production from the repo. The active server env file is /opt/kayanou/.env.

You can use .env.example as a starting point:

cp .env.example /opt/kayanou/.env
nano /opt/kayanou/.env

3.1 Required variables

DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DB_NAME?sslmode=require
NODE_ENV=production
APP_ENV=staging
JWT_SECRET=replace_with_a_long_random_secret
JWT_VERSION=1
BACKEND_URL=http://kayanou-service:8080
NEXT_PUBLIC_BACKEND_URL=https://stage.api.silab.website
CORS_ALLOWED_ORIGINS=https://stage.silab.website
CORS_ALLOW_CREDENTIALS=true
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
RESEND_API_KEY=your_resend_api_key
CONTACT_EMAIL_FROM=email_that_send_the_contact_requests  (must be from a domain you have configured on Resend.com)
CONTACT_EMAIL_TO=email_that_will_receive_contact_requests

For production, change these values:

APP_ENV=production
NEXT_PUBLIC_BACKEND_URL=https://api.silab.website
CORS_ALLOWED_ORIGINS=https://silab.website

3.2 Important notes about env handling

  • DATABASE_URL is required on staging and production because docker-compose.prod.yml does not start a PostgreSQL container.
  • BACKEND_URL is used by server-side Next.js code and must stay http://kayanou-service:8080 inside Docker.
  • NEXT_PUBLIC_BACKEND_URL is compiled into the frontend image at build time. If it changes, redeploy with ./deploy.sh so the frontend is rebuilt.
  • APP_ENV is used for Cloudinary folder naming and should be staging or production on the VM.
  • POSTGRES_USER and POSTGRES_PASSWORD exist in local env templates, but they are not used by docker-compose.prod.yml.
  • Do not reuse secrets from tracked example env files. Store real secrets only in /opt/kayanou/.env or GitHub secrets.

Step 4: Configure NGINX

This repo includes host-level NGINX templates:

  • nginx/nginx.stage.txt
  • nginx/nginx.prod.txt

They proxy:

  • frontend traffic to 127.0.0.1:3000
  • backend traffic to 127.0.0.1:8080

Example production setup:

sudo cp nginx/nginx.prod.txt /etc/nginx/nginx.conf
sudo nginx -t
sudo systemctl restart nginx

Example staging setup:

sudo cp nginx/nginx.stage.txt /etc/nginx/nginx.conf
sudo nginx -t
sudo systemctl restart nginx

4.1 TLS note

The checked-in NGINX configs only define HTTP listeners on port 80. The live site URLs use https, so TLS must be terminated elsewhere or added separately on the VM.

Step 5: Deploy manually

From the repo root:

chmod +x deploy.sh
./deploy.sh

deploy.sh does three things:

  1. Runs docker compose -f docker-compose.prod.yml --env-file /opt/kayanou/.env up -d --build
  2. Runs npx prisma migrate deploy inside the backend container
  3. Restarts the backend container

Step 6: Automated deployment with GitHub Actions

6.1 Continuous integration

.github/workflows/ci.yml runs on the self-hosted runner for pushes and pull requests on dev. It:

  • installs frontend dependencies and runs lint, typecheck, and build
  • installs backend dependencies and runs lint, typecheck, and build

6.2 Staging deployment

.github/workflows/stage.yml runs when a pull request into dev is merged, or when triggered manually.

It SSHes to the staging host and runs:

cd ~/W26project-KAYANOU
git fetch origin
git reset --hard origin/dev
git clean -fdx
git checkout -B dev origin/dev
chmod +x deploy.sh
./deploy.sh

6.3 Production deployment

.github/workflows/prod.yml runs when a pull request into main is merged, or when triggered manually.

It SSHes to the production host and runs:

cd ~/W26project-KAYANOU
git fetch origin
git reset --hard origin/main
git clean -fdx
chmod +x deploy.sh
./deploy.sh

6.4 Deployment warning

The workflow uses git reset --hard and git clean -fdx on the server checkout. Do not keep manual edits, local uploads, or untracked files inside ~/W26project-KAYANOU.

Step 7: Verify the deployment

7.1 Check containers

docker compose -f docker-compose.prod.yml --env-file /opt/kayanou/.env ps

Expected services:

  • kayanou-service
  • next-fe

7.2 Check logs

docker compose -f docker-compose.prod.yml --env-file /opt/kayanou/.env logs -f

Backend only:

docker compose -f docker-compose.prod.yml --env-file /opt/kayanou/.env logs -f kayanou-service

Frontend only:

docker compose -f docker-compose.prod.yml --env-file /opt/kayanou/.env logs -f next-fe

7.3 Check health endpoints

curl http://127.0.0.1:8080/health
curl -I http://127.0.0.1:3000

If NGINX and DNS are configured, also verify:

curl -I https://stage.silab.website
curl -I https://stage.api.silab.website/health
curl -I https://silab.website
curl -I https://api.silab.website/health

Step 8: Local Docker development

Use this when you want the full stack on your own machine.

8.1 Start

./dev-start.sh

If .env does not exist, dev-start.sh copies it from .env.example.

8.2 Local service URLs

  • Frontend: http://localhost:3000
  • Backend: http://localhost:8080
  • PostgreSQL: localhost:5432

8.3 Stop

./dev-stop.sh

Troubleshooting

Frontend is calling the wrong backend

The frontend bakes NEXT_PUBLIC_BACKEND_URL into the image during docker build. Update /opt/kayanou/.env, then rerun ./deploy.sh.

Login works locally but not on a custom production domain

kayanou-service/src/routes/auth.ts currently hardcodes the production cookie domain to .silab.website and forces sameSite: "none" when NODE_ENV=production. If you deploy under a different domain, update that code before release.

Image upload features fail

Author and team image uploads depend on Cloudinary credentials. Verify:

  • CLOUDINARY_CLOUD_NAME
  • CLOUDINARY_API_KEY
  • CLOUDINARY_API_SECRET

Contact-Form emails don't send

Contact-form emails depend on Resend credentials. Verify:

  • RESEND_API_KEY
  • CONTACT_EMAIL_FROM
  • CONTACT_EMAIL_TO

Database migrations fail

Check connectivity to the external database:

grep DATABASE_URL /opt/kayanou/.env
docker compose -f docker-compose.prod.yml --env-file /opt/kayanou/.env logs kayanou-service

NGINX is up but the site is unreachable

Check:

  • DNS points to the VM
  • NGINX config matches the correct domain names
  • ports 80 and 443 are open
  • ports 3000 and 8080 are not being blocked from NGINX on localhost

Supporting files

  • deploy.sh
  • docker-compose.yml
  • docker-compose.prod.yml
  • .github/workflows/ci.yml
  • .github/workflows/stage.yml
  • .github/workflows/prod.yml
  • nginx/nginx.stage.txt
  • nginx/nginx.prod.txt