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 port3000 - Backend: Express and Prisma API in
kayanou-service, exposed from the container on port8080 - Database:
- Local development uses the
postgrescontainer fromdocker-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:3000and127.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_KEYSSH_HOSTfor stagingSSH_HOST_PRODfor 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_URLis required on staging and production becausedocker-compose.prod.ymldoes not start a PostgreSQL container.BACKEND_URLis used by server-side Next.js code and must stayhttp://kayanou-service:8080inside Docker.NEXT_PUBLIC_BACKEND_URLis compiled into the frontend image at build time. If it changes, redeploy with./deploy.shso the frontend is rebuilt.APP_ENVis used for Cloudinary folder naming and should bestagingorproductionon the VM.POSTGRES_USERandPOSTGRES_PASSWORDexist in local env templates, but they are not used bydocker-compose.prod.yml.- Do not reuse secrets from tracked example env files. Store real secrets only in
/opt/kayanou/.envor GitHub secrets.
Step 4: Configure NGINX
This repo includes host-level NGINX templates:
nginx/nginx.stage.txtnginx/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:
- Runs
docker compose -f docker-compose.prod.yml --env-file /opt/kayanou/.env up -d --build - Runs
npx prisma migrate deployinside the backend container - 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, andbuild - installs backend dependencies and runs
lint,typecheck, andbuild
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-servicenext-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_NAMECLOUDINARY_API_KEYCLOUDINARY_API_SECRET
Contact-Form emails don't send
Contact-form emails depend on Resend credentials. Verify:
RESEND_API_KEYCONTACT_EMAIL_FROMCONTACT_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
80and443are open - ports
3000and8080are not being blocked from NGINX on localhost
Supporting files
deploy.shdocker-compose.ymldocker-compose.prod.yml.github/workflows/ci.yml.github/workflows/stage.yml.github/workflows/prod.ymlnginx/nginx.stage.txtnginx/nginx.prod.txt