Files
hackerspace.sh/docs/DEPLOYMENT.md
T
v0andMoheeb Zara 520c04eadb feat: fully reset and rebuild database with comprehensive guide
Database reset, indexes, RLS policies, triggers, and updated deployment guide

Co-authored-by: Moheeb Zara <[email protected]>
2026-03-11 06:06:58 +00:00

16 KiB

Hackerspace.sh — Deployment Guide

Complete instructions for every supported deployment target.


Table of Contents

  1. Prerequisites
  2. Supabase Setup (required for all targets)
  3. Environment Variables Reference
  4. Option A — Vercel (recommended)
  5. Option B — DigitalOcean App Platform
  6. Option C — DigitalOcean Droplet (Docker / self-hosted)
  7. Post-Deploy Checklist
  8. Upgrading an Existing Deployment
  9. Troubleshooting

1. Prerequisites

Tool Version Purpose
Node.js 20+ Local development
pnpm 9+ Package manager
Git any Source control
Supabase account free tier works Database + auth + realtime
Vercel / DigitalOcean account — Hosting

Clone the repo:

git clone https://github.com/virgilvox/hackerspace-management.git
cd hackerspace-management
pnpm install

2. Supabase Setup

This step is identical regardless of which hosting option you choose.

2.1 Create a Supabase project

  1. Go to supabase.com → New project
  2. Choose a region close to your users
  3. Set a strong database password and save it — you won't need it directly but keep it safe
  4. Wait ~2 minutes for provisioning

2.2 Apply the schema

  1. In the Supabase dashboard, go to SQL Editor
  2. Click New query
  3. Paste the entire contents of scripts/schema.sql
  4. Click Run (or Ctrl+Enter)

You should see no errors. The script is idempotent — safe to re-run.

2.3 Configure Auth

In the Supabase dashboard:

Authentication → Settings → General

  • Set Site URL to your app's public URL (e.g. https://yourapp.vercel.app)
  • Add the same URL to Redirect URLs
  • Also add http://localhost:3000 for local dev

Authentication → Settings → Email

  • Enable Confirm email if you want verified signups
  • Customize the email templates as needed

Authentication → Providers

  • Email/Password is enabled by default — no changes needed
  • Optionally enable Google, GitHub, etc. (social login buttons are wired in the UI)

2.4 Enable Realtime

The schema script already runs ALTER PUBLICATION supabase_realtime ADD TABLE ... for comms_messages and comms_channels. Verify in:

Database → Replication — confirm both tables appear under supabase_realtime.

2.5 Get your API keys

Project Settings → API

Copy these four values — you'll need them in every deployment:

NEXT_PUBLIC_SUPABASE_URL        = https://xxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY   = eyJhbGci...
SUPABASE_SERVICE_ROLE_KEY       = eyJhbGci...   (keep this SECRET — server-side only)

2.6 Create your first Space + Admin user

After deploying the app, navigate to /signup. The signup flow will:

  1. Create an auth.users row in Supabase
  2. Trigger handle_space_signup() which inserts a space_members row

To make yourself an admin, run this in the SQL editor (replace values):

UPDATE public.space_members
SET role = 'admin', status = 'current'
WHERE email = '[email protected]';

3. Environment Variables

Create .env.local for local dev. For deployed environments, set these in your host's dashboard.

# ── Supabase ──────────────────────────────────────────────────
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...

# ── App ───────────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL=https://yourdomain.com

# ── PayPal (optional — set in app Settings UI too) ────────────
# These are also stored per-space in the integrations table.
# The API route reads from the DB, not from env vars.
# No env vars needed for PayPal.

# ── Node ──────────────────────────────────────────────────────
NODE_ENV=production

Never commit .env.local to git. The .gitignore already excludes it.


4. Option A — Vercel

The simplest and fastest path. Zero server management.

4.1 Deploy via Vercel CLI

# Install Vercel CLI globally
pnpm add -g vercel

# From the project root:
vercel

# Follow the prompts:
#   - Link to existing project or create new
#   - Framework: Next.js (auto-detected)
#   - Root directory: ./  (default)

4.2 Set environment variables

vercel env add NEXT_PUBLIC_SUPABASE_URL production
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY production
vercel env add SUPABASE_SERVICE_ROLE_KEY production
vercel env add NEXT_PUBLIC_APP_URL production

Or set them in the Vercel dashboard: Project → Settings → Environment Variables

4.3 Deploy to production

vercel --prod

4.4 Connect a custom domain (optional)

Vercel dashboard → Domains → add your domain → follow DNS instructions.

4.5 Continuous deployment

Push to main branch → Vercel auto-deploys. Push to any other branch → preview deployment at a unique URL.

git push origin main  # triggers production deploy
git push origin feat/my-feature  # triggers preview deploy

5. Option B — DigitalOcean App Platform

Managed container hosting. No Docker knowledge required. Scales automatically.

5.1 Push your repo to GitHub

DigitalOcean App Platform deploys directly from GitHub.

git remote add origin https://github.com/YOUR_ORG/hackerspace-management.git
git push -u origin main

5.2 Create the app

  1. Go to cloud.digitalocean.com/apps
  2. Click Create App → GitHub → authorize and select your repo
  3. Select branch: main
  4. DigitalOcean auto-detects Next.js — confirm the build settings:
    • Build command: pnpm install && pnpm build
    • Run command: pnpm start
    • HTTP port: 3000

5.3 Set environment variables

In the App Platform UI, go to Settings → App-Level Environment Variables and add:

NEXT_PUBLIC_SUPABASE_URL        = (your value)
NEXT_PUBLIC_SUPABASE_ANON_KEY   = (your value)
SUPABASE_SERVICE_ROLE_KEY       = (mark as Encrypted)
NEXT_PUBLIC_APP_URL             = https://your-app.ondigitalocean.app
NODE_ENV                        = production

Mark SUPABASE_SERVICE_ROLE_KEY as Encrypted so it's not visible in logs.

5.4 Choose a plan

  • Basic ($5/mo): fine for small hackerspaces (<50 members)
  • Professional ($12/mo): recommended for production — includes more RAM and 99.99% SLA

5.5 Deploy

Click Create Resources → wait ~3 minutes for the first build.

5.6 Custom domain

App Platform dashboard → Settings → Domains → add domain → update your DNS with the CNAME provided.

5.7 Continuous deployment

App Platform auto-deploys on every push to main by default. You can also configure it to deploy only on tagged releases.


6. Option C — DigitalOcean Droplet

Full self-hosted on a VPS. Maximum control. Use this if you want everything on your own infrastructure.

6.1 Create a Droplet

  1. Go to cloud.digitalocean.com/droplets
  2. Create Droplet
    • Image: Ubuntu 24.04 LTS
    • Plan: Basic — Regular — $12/mo (2 vCPU, 2GB RAM) minimum; $24/mo recommended
    • Datacenter: closest to your users
    • Authentication: SSH key (strongly recommended over password)
  3. Note the Droplet IP address

6.2 Initial server setup

SSH in and harden the server:

ssh root@YOUR_DROPLET_IP

# Create a non-root user
adduser deploy
usermod -aG sudo deploy

# Copy SSH key to new user
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

# Switch to deploy user
su - deploy

6.3 Install Docker and Docker Compose

# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker deploy

# Install Docker Compose plugin
sudo apt-get install -y docker-compose-plugin

# Verify
docker --version
docker compose version

6.4 Install Nginx and Certbot

sudo apt-get update
sudo apt-get install -y nginx certbot python3-certbot-nginx

6.5 Clone the repo on the server

cd /opt
sudo git clone https://github.com/YOUR_ORG/hackerspace-management.git hackerspace
sudo chown -R deploy:deploy /opt/hackerspace
cd /opt/hackerspace

6.6 Create the Docker Compose file

cat > /opt/hackerspace/docker-compose.yml << 'EOF'
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: hackerspace_app
    restart: unless-stopped
    ports:
      - "3000:3000"
    env_file:
      - .env.production
    environment:
      NODE_ENV: production
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  default:
    name: hackerspace_net
EOF

6.7 Create the Dockerfile

cat > /opt/hackerspace/Dockerfile << 'EOF'
FROM node:20-alpine AS base
RUN corepack enable pnpm

FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm 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 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]
EOF

6.8 Enable standalone output in Next.js config

Edit next.config.mjs and ensure it contains:

const nextConfig = {
  output: 'standalone',
  // ...rest of your config
}

6.9 Create the production env file

cat > /opt/hackerspace/.env.production << 'EOF'
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...
NEXT_PUBLIC_APP_URL=https://yourdomain.com
NODE_ENV=production
EOF

# Lock down permissions — this file contains secrets
chmod 600 /opt/hackerspace/.env.production

6.10 Build and start the container

cd /opt/hackerspace
docker compose build
docker compose up -d

# Check logs
docker compose logs -f app

6.11 Configure Nginx as reverse proxy

sudo nano /etc/nginx/sites-available/hackerspace

Paste:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass         http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection 'upgrade';
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/hackerspace /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

6.12 SSL with Let's Encrypt

Point your domain's A record to the Droplet IP first, then:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot will auto-configure HTTPS and set up auto-renewal. Verify renewal works:

sudo certbot renew --dry-run

6.13 Set up automatic deployments (optional)

Create a deploy script:

cat > /opt/hackerspace/deploy.sh << 'EOF'
#!/bin/bash
set -e
cd /opt/hackerspace
git pull origin main
docker compose build app
docker compose up -d --no-deps app
docker image prune -f
echo "Deploy complete: $(date)"
EOF
chmod +x /opt/hackerspace/deploy.sh

To trigger a deploy from your local machine:

ssh deploy@YOUR_DROPLET_IP '/opt/hackerspace/deploy.sh'

Or set up a GitHub Actions workflow:

# .github/workflows/deploy.yml
name: Deploy to Droplet

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DROPLET_IP }}
          username: deploy
          key: ${{ secrets.DROPLET_SSH_KEY }}
          script: /opt/hackerspace/deploy.sh

Add DROPLET_IP and DROPLET_SSH_KEY to your GitHub repo's Settings → Secrets.

6.14 Firewall

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status

7. Post-Deploy Checklist

Run through this after every fresh deployment:

  • App loads at your domain with HTTPS
  • /signup creates a user and redirects to /dashboard
  • Dashboard shows "No members yet" (not an error)
  • Supabase dashboard → Authentication → Users shows the signup
  • space_members table has a row for the new user
  • Chat page loads channels (general, announcements, ops)
  • Send a message — it appears immediately without page refresh
  • Upgrade your user to admin via SQL (see section 2.6)
  • Settings page → save space settings → no error toast
  • PayPal integration → save credentials → "LIVE" badge appears

8. Upgrading

When the schema changes, a new numbered script will be added to scripts/. For example scripts/012_add_feature.sql. Run only the new script in the Supabase SQL editor — do not re-run schema.sql on an existing database.

scripts/
  schema.sql          ← fresh deployment only
  012_add_feature.sql ← incremental upgrade
  013_...sql

For Vercel / App Platform: push to main, the build auto-deploys.

For Droplet: ssh deploy@IP '/opt/hackerspace/deploy.sh'


9. Troubleshooting

"relation does not exist" errors

The schema was not applied or was partially applied. Re-run scripts/schema.sql in the Supabase SQL editor.

Users can sign up but see no data

The handle_space_signup trigger is not firing or the space_id metadata was not passed during signup. Check:

SELECT * FROM public.space_members ORDER BY joined_at DESC LIMIT 5;

If empty, ensure a spaces row exists first:

INSERT INTO public.spaces (name, slug, invite_code)
VALUES ('My Hackerspace', 'my-hackerspace', 'INVITE123');

Then re-run signup with the space's id in the metadata.

Chat messages not appearing in realtime

Verify realtime is enabled:

SELECT * FROM pg_publication_tables WHERE pubname = 'supabase_realtime';

Both comms_messages and comms_channels must appear. If not:

ALTER PUBLICATION supabase_realtime ADD TABLE public.comms_messages;
ALTER PUBLICATION supabase_realtime ADD TABLE public.comms_channels;

500 errors on API routes

Check that SUPABASE_SERVICE_ROLE_KEY is set in your deployment environment. This key is required for server-side actions.

Docker container exits immediately

docker compose logs app

Most common cause: missing .env.production file or a missing required env var.

SSL certificate not renewing

sudo systemctl status certbot.timer
sudo journalctl -u certbot

If the timer is not running: sudo systemctl enable --now certbot.timer