Setup
Alchemify runs on Node.js and PostgreSQL. No Docker required.
Prerequisites
Section titled “Prerequisites”- PostgreSQL 14+ installed and running
psqlcommand available- Node.js 20+, pnpm
1. Create the database
Section titled “1. Create the database”Connect as a PostgreSQL superuser (typically postgres):
psql -U postgres -c "CREATE DATABASE alchemify"2. Apply the schema
Section titled “2. Apply the schema”The schema creates all roles, tables, RLS policies, and auth functions. It is idempotent — safe to re-run. First generate the page SQL, then apply the schema:
cd apps/server && bash seed-pages.shpsql -U postgres -d alchemify -f apps/server/schema.sqlThis creates:
| Role | Purpose |
|---|---|
authenticator | Login role for the connection pool. Cannot access data directly. |
owner | Full access (identical to admin at DB level; org-management distinction is app-level) |
admin | Full access to all data |
staff | Org-scoped access (limited by RLS) |
member | Read-only access to business data |
anon | Unauthenticated access (very limited) |
The authenticator role is granted the ability to SET ROLE to the five app roles. The server uses this to impersonate the appropriate role per request.
3. Configure the environment
Section titled “3. Configure the environment”cp apps/server/.env.example apps/server/.envThe default .env connects as authenticator with the password from apps/server/schema.sql:
DATABASE_URL=postgres://authenticator:authenticator_password_change_me@localhost:5432/alchemifyJWT_SECRET=dev-secret-change-in-productionLOG_LEVEL=debugMost settings (port, JWT expiration, file upload limits, etc.) have sensible defaults defined in apps/server/src/config.ts. LOG_LEVEL controls structured logging output (see Logging). For local development the defaults work as-is. For anything shared, change the authenticator password in both apps/server/schema.sql and .env.
4. Verify the setup
Section titled “4. Verify the setup”# Confirm authenticator can connectpsql "postgres://authenticator:authenticator_password_change_me@localhost:5432/alchemify" \ -c "SELECT current_user"
# Confirm role switching workspsql "postgres://authenticator:authenticator_password_change_me@localhost:5432/alchemify" \ -c "SET ROLE 'anon'; SELECT current_user"Both should succeed. If the first fails, check that the authenticator role exists and the password matches.
5. Install and run
Section titled “5. Install and run”pnpm installpnpm devVerify with:
curl -s http://localhost:3000/health# {"status":"ok"}6. Run tests
Section titled “6. Run tests”Tests run against the same alchemify database. They truncate tables between runs.
pnpm test7. Lint & format
Section titled “7. Lint & format”The project uses ESLint and Prettier. Run them to verify code quality:
pnpm lint # check for lint errorspnpm format:check # verify formattingpnpm format # auto-fix formattingDocker alternative
Section titled “Docker alternative”If you prefer Docker for local PostgreSQL, a single command gives you a fully initialized database:
docker compose upThis starts a postgres:16 container, creates the alchemify database, and applies all schema files automatically on first startup. By default this includes chat-schema.sql; to skip it, set INCLUDE_CHAT: "false" in the postgres environment section of docker-compose.yml.
The default DATABASE_URL in .env.example works without changes:
DATABASE_URL=postgres://authenticator:authenticator_password_change_me@localhost:5432/alchemifyNotes:
- Initialization runs only on first startup (when the data volume is empty). Subsequent
docker compose upcalls reuse the existing data. - If you have a local PostgreSQL already running on port 5432, stop it first or you’ll get a port conflict.
- To reset the database and start fresh:
docker compose down -v && docker compose upAfter the container is running, continue from step 3 above.
Container setup (no systemd)
Section titled “Container setup (no systemd)”If you’re running inside a container where systemd is not available, these extra steps are needed.
Start / stop / restart PostgreSQL
Section titled “Start / stop / restart PostgreSQL”systemctl won’t work — use pg_ctlcluster directly:
sudo pg_ctlcluster 14 main startsudo pg_ctlcluster 14 main stopsudo pg_ctlcluster 14 main restartPeer authentication
Section titled “Peer authentication”The default pg_hba.conf uses peer auth, which requires matching the OS user to the PostgreSQL user. You can’t run psql -U postgres as a non-postgres OS user.
Fix: change local auth from peer to md5 in pg_hba.conf:
# Find the filesudo -u postgres psql -c "SHOW hba_file"# Edit it — change "peer" to "md5" on the local lines (keep peer for postgres user)sudo vi /etc/postgresql/14/main/pg_hba.conf
# Restart PostgreSQLsudo pg_ctlcluster 14 main restartAfter this, psql -U postgres works directly (with password prompt) — no more sudo -u postgres prefix.
Alternative (if you don’t want to edit pg_hba.conf): prefix commands with sudo -u postgres:
sudo -u postgres psql -c "CREATE DATABASE alchemify"“could not change directory” warning
Section titled ““could not change directory” warning”When running sudo -u postgres from a directory the postgres user can’t access (like /home/user/...), you get:
could not change directory to "/home/user/project": Permission deniedThis is a warning, not an error — the command still runs. But file paths passed to -f must be accessible to the postgres user.
Workaround: copy the schema to a world-readable location:
cp apps/server/schema.sql /tmp/schema.sqlsudo -u postgres psql -d alchemify -f /tmp/schema.sqlAdmin user for dev
Section titled “Admin user for dev”Rather than using sudo -u postgres for everything, create a dedicated PostgreSQL user with full access and a .pgpass file for passwordless login:
# As postgres superuser:sudo -u postgres psql <<'SQL'CREATE USER dba WITH PASSWORD 'your-password-here';GRANT ALL PRIVILEGES ON DATABASE alchemify TO dba;\c alchemifyGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO dba;GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO dba;GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO dba;ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO dba;ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO dba;ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON FUNCTIONS TO dba;SQLThen set up passwordless access:
echo "localhost:5432:alchemify:dba:your-password-here" >> ~/.pgpasschmod 600 ~/.pgpassNow you can connect directly: psql -U dba alchemify
Container full setup sequence
Section titled “Container full setup sequence”# 1. Start PostgreSQL (no systemd)sudo pg_ctlcluster 14 main start
# 2. Change pg_hba.conf local auth from peer to md5 (keep peer for postgres user)sudo vi /etc/postgresql/14/main/pg_hba.confsudo pg_ctlcluster 14 main restart
# 3. Create the databasesudo -u postgres psql -c "CREATE DATABASE alchemify"
# 4. Apply schema (copy to accessible path first)cp apps/server/schema.sql /tmp/schema.sqlsudo -u postgres psql -d alchemify -f /tmp/schema.sql
# 5. Create admin user (see "Admin user for dev" above)
# 6. Set authenticator password (must match .env)psql -U dba alchemify -c "ALTER ROLE authenticator WITH PASSWORD 'authenticator_password_change_me'"
# 7. Verifypsql -U dba alchemify -c "SELECT current_user"Troubleshooting
Section titled “Troubleshooting”ECONNREFUSED 127.0.0.1:5432 — PostgreSQL is not running. Start it:
# Linux (systemd)sudo systemctl start postgresql
# macOS (Homebrew)brew services start postgresql
# Container (no systemd)sudo pg_ctlcluster 14 main startrole "authenticator" does not exist — Schema hasn’t been applied. Re-run step 2.
password authentication failed for user "authenticator" — Password mismatch between .env and the role in PostgreSQL. Either re-apply apps/server/schema.sql (which uses IF NOT EXISTS and won’t reset an existing password) or alter it manually:
ALTER ROLE authenticator WITH PASSWORD 'authenticator_password_change_me';permission denied for table users — You connected as authenticator directly. This is expected — authenticator has no table access. The server calls SET LOCAL ROLE to switch to an app role before querying.
Re-applying the schema — apps/server/schema.sql uses IF NOT EXISTS for roles/tables and CREATE OR REPLACE for functions, so it is safe to re-run at any time. However, CREATE POLICY will error if a policy already exists. To do a clean reset:
psql -U postgres -c "DROP DATABASE alchemify"psql -U postgres -c "CREATE DATABASE alchemify"psql -U postgres -d alchemify -f apps/server/schema.sqlContainer: pg_ctlcluster start warning — The “connection to the database failed” warning after switching to md5 auth is harmless — the startup check can’t authenticate, but the server starts fine.
Container: NOTICE messages during schema apply — Messages like “role already a member” or “trigger does not exist, skipping” are harmless — the schema uses IF NOT EXISTS and DROP ... IF EXISTS.