Self-hosting Supabase (Part 1?)

Self-hosting Supabase (Part 1?)
Photo by Tim Gouw / Unsplash

First off, I'm grateful that Supabase offers a self-hosted option. And they did put a lot of work into it. But there are lots of holes in documentation and hurdles to clear to get things running smoothly. Hopefully these notes help someone else other than me. I'll post an update at some point with additional notes and updates.

Some of this may not make sense because they were notes I wrote for myself. If something isn't clear, comment here if you can or reach out to me on X. For readability, I would copy this out into a file editor (that can handle markdown) of your choice. Otherwise it's tough to read.

## Supebase Notes
- If starting Supabase works, BUT you're getting auth errors, make sure that when you run `docker-compose up -d`, your environment isn't overriding any env vars in the `.env` file! This was happening to me and SUPER confusing.
- Generate project ref ID using something like `"".join([random.choice(string.ascii_lowercase) for x in range(20)])`. Set that as `POOLER_TENANT_ID` in the .env file for the Supabase docker-compose.yml file. Then make sure your `.env` file for this project has the postgres username set to `postgres.POOLER_TENANT_ID`.
    - Also note: I started getting a random error about SSL and `(node:1) V8: file:///usr/src/app/dist/server/app.js:8 'assert' is deprecated...`. If you get this, use port 6543 for generating types instead of 5432. Not sure why, but this fixed it for me.
- The `VAULT_ENC_KEY` in the .env file for the Supabase docker-compose.yml file MUST be 32 characters long (i.e. 16 bytes)
- The `GOOGLE_REDIRECT_URI` MUST be `http://localhost:8000/auth/v1/callback`. When self-hosting Supabase, 8000 is the Kong service port (i.e. API port).
- Username for DB is `postgres.POOLER_TENANT_ID` (which is set in the .env file for the Supabase docker-compose.yml file)
   - Command for pushing migrations is: `npx supabase migration up --db-url postgres://postgres.{pooler_tenant_id}:{password}@localhost:5432/postgres`
      - Using environment variables: `npx supabase migration up --db-url "postgresql://${PG_USERNAME}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/${PG_DATABASE}"`
- You need to create a JWT_SECRET *first*, then create the anon and service role keys. And Supabase's tool DOES NOT WORK! I had to ask Claude 3.7 to create a script to generate the keys for me, and *that* worked. This was confusing because the .env.example file provided by Supabase had a garbage/default JWT_SECRET value, but seemingly-valid service role and anon keys.
- For file storage via supabase, at least on Mac, you need to enable VirtioFS in Docker Desktop settings. Otherwise all file uploads will fail.
- Generate up to date types using this: `npx supabase gen types typescript --db-url "postgresql://${PG_USERNAME}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/${PG_DATABASE}" > database.types.ts`
    - UPDATE: After running into the SSL issue with generating types and trying to connect to port 5432, we need to use port 6543 for this instead!
- For the email auth part in your supabase/docker/.env file, this is what you need in order to disable sending the confirmation email and auto-confirm users:
```bash
## Email auth
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=true
SMTP_ADMIN_EMAIL=
SMTP_HOST=
SMTP_PORT=2500
SMTP_USER=
SMTP_PASS=
SMTP_SENDER_NAME=
ENABLE_ANONYMOUS_USERS=false
```
- If you get a `NEXT_REDIRECT` error code, it's likely because you have a NextJS `redirect()` call inside a `try/catch` block. You need to move the `redirect()` call outside of the block and let NextJS handle the redirect. Super weird... that doesn't seem like it should be designed that way.

The script I had Claude create to help me generate the Service Role and Anon keys is below:

/*
Run this using:
node helper_scripts/gen-keys.js

// Generate keys with:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

// Generate base64 encoded keys with:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Install dependencies using:
npm install --save-dev crypto base64url
 */

const crypto = require('crypto');
const base64url = require('base64url');

function generateSupabaseKey(role, jwtSecret) {
  // Create the header
  const header = {
    alg: 'HS256',
    typ: 'JWT'
  };
  
  // Create the payload with required claims
  const now = Math.floor(Date.now() / 1000);
  const payload = {
    role: role,
    iss: "supabase",
    iat: now,
    exp: now + (60 * 60 * 24 * 365 * 5) // 5 years expiration
  };
  
  // Base64Url encode the header and payload
  const encodedHeader = base64url(JSON.stringify(header));
  const encodedPayload = base64url(JSON.stringify(payload));
  
  // Create the content to be signed
  const signatureContent = `${encodedHeader}.${encodedPayload}`;
  
  // Create the signature
  const signature = crypto
    .createHmac('sha256', jwtSecret)
    .update(signatureContent)
    .digest();
  
  // Base64Url encode the signature
  const encodedSignature = base64url(signature);
  
  // Combine to create the JWT token
  return `${signatureContent}.${encodedSignature}`;
}

// Example usage
const JWT_SECRET = process.env.SUPABASE_JWT_SECRET || 'your-jwt-secret-here';

const serviceRoleKey = generateSupabaseKey('service_role', JWT_SECRET);
const anonKey = generateSupabaseKey('anon', JWT_SECRET);

console.log('SERVICE_ROLE Key:');
console.log(serviceRoleKey);
console.log('\nANON Key:');
console.log(anonKey);