A local Stampwing installation needs Node.js 22+, PostgreSQL 17, and the source repository. Run it in explicit demo mode with its own database and encryption keys. The web app and worker are separate processes.
Before you start #
- For
- Developers who already have access to the Stampwing source repository.
- Bring
- Node.js 22+, npm, Git, OpenSSL, and Docker Compose v2 or your own PostgreSQL 17 instance. Shell commands use macOS/Linux or WSL2. On Windows, run the source-install commands inside WSL2 with Docker integration enabled; do not mix Windows npm with a Linux node_modules folder.
- Scope
- This is a local development installation. No public clone URL is advertised here. Obtain the repository from your project owner; these instructions do not provision a production service.
1. Open the repository and check your tools #
Run all commands on this page from the repository root: the folder containing package.json, .env.example, and docker-compose.yml. If you have a source archive, unpack it first. Use a dedicated database; never point this tutorial at another application or production data.
node --version
npm --version
docker compose version
openssl version
npm ci2. Create a private environment file #
Use a dedicated development checkout. Do not repurpose an existing live installation or switch its database to demo. Copy .env.example to .env.local only if .env.local does not already exist. If an existing file belongs to this same local demo, edit it in place and preserve its secrets. Next.js and the database/worker scripts read this file. Keep this checkout’s settings in .env.local. A .env.development.local file can override Next.js without overriding the database scripts or worker. An already-exported shell variable can also override file configuration; check these sources if the processes appear to use different settings. Never print secret values into a shared log.
- Use the first random value for SESSION_SECRET and the second for MESSAGE_ENCRYPTION_KEY. Each output is 64 hexadecimal characters. Keep them private and do not reuse one value for both fields.
- Set the values in the next section. The Compose database uses host port 5434; the sample environment file’s generic port 5432 must be changed for this setup.
- For this single-database local demo, leave MIGRATION_DATABASE_URL, WORKER_DATABASE_URL, and OPERATOR_DATABASE_URL empty. Existing nonempty overrides can send migrations or workers to a different database. Do not change another installation’s configuration to clear them.
- Leave AWS, SES, Stripe, Clerk, and other production integrations unconfigured for this local demo. Do not copy credentials from another application.
- Keep .env.local ignored by Git. Save the encryption key securely: replacing it can make existing encrypted message content unreadable.
if [ ! -e .env.local ]; then cp .env.example .env.local; fi
openssl rand -hex 32
openssl rand -hex 323. Set these local values #
The web app runs at port 3017. PostgreSQL is exposed only on loopback at port 5434. Keep the Compose project name postlane; it preserves the expected volume identity. This walkthrough disables automatic setup so database changes happen explicitly. Choose one database option below, then continue to initialization. Skip Docker prerequisite commands if you choose the existing-PostgreSQL option.
DATABASE_URL=postgresql://postlane:postlane@localhost:5434/postlane
MIGRATION_DATABASE_URL=
WORKER_DATABASE_URL=
OPERATOR_DATABASE_URL=
POSTLANE_MODE=demo
POSTLANE_COMMERCIAL=false
POSTLANE_AUTO_SETUP=false
POSTRUNE_PUBLIC_SITE_ONLY=false
POSTRUNE_ASSISTANTS_ENABLED=false
APP_ORIGIN=http://127.0.0.1:3017
SESSION_SECRET=REPLACE_WITH_FIRST_RANDOM_VALUE
MESSAGE_ENCRYPTION_KEY=REPLACE_WITH_SECOND_RANDOM_VALUE
WORKER_ROLES=dispatch,feedback4A. Database option: the provided Compose service #
Use this option for the standard local walkthrough. Skip it if you choose option 4B. Start Docker Desktop or your Docker daemon first; docker compose version alone only proves the command is installed.
docker info
docker compose up -d --wait postgres
docker compose ps
docker compose exec postgres pg_isready -U postlane -d postlane4B. Alternative: a dedicated local PostgreSQL server #
Skip this option if you used Compose. Use only a dedicated local PostgreSQL 17 server, not a shared or production server. Have its PostgreSQL administrator create the new login and database below. Add the correct --host, --port, and --username options when your administrator connection differs from the command-line defaults.
This simple personal-demo setup uses a dedicated local administrator login, matching the provided Compose service. Current migrations create roles, and worker-only tables require privileged access. An ordinary database owner is insufficient: setup can fail with “permission denied to create role” or a row-level security error. Run these commands as the administrator of an isolated development PostgreSQL server. Never grant these privileges to an existing shared or production application role, and do not disable row-level security. Production uses separate migration, restricted application, and worker roles from the deployment runbook.
createuser --pwprompt --superuser postlane_dev
createdb --owner=postlane_dev postlane_dev5. Initialize the chosen database #
After exactly one database option is ready and DATABASE_URL points to it, run these commands from the repository root. Stop if either fails. The seed command creates labelled sample data and never enables Live sending.
npm run db:migrate && npm run db:seed6. Run the web app and worker in separate terminals #
Open http://127.0.0.1:3017/app. The app should label the environment as demo/Test. The worker should report “Stampwing demo workers: dispatch, feedback.” Keep that terminal open and check for errors after the startup line. A healthy web page alone does not prove a worker is processing queued messages. A row-level security error in this local demo means the database-role requirements in option 4B have not been met.
# Terminal A: keep running
npm run dev
# Terminal B: keep running
npm run worker7. Verify the installation #
Expect JSON with ok: true, mode: "demo", and database: "ready". Then use the quickstart to queue a Test message. Its mode should be demo, and its UUID should appear in the same project’s Messages view. This checks persistence and application connection; it does not check real DNS or provider delivery.
- Connection refused on port 3017: start the web app or fix its startup error.
- Database connection refused: check Compose health and port 5434 in DATABASE_URL.
- Messages remain queued: check the worker terminal, project pause state, and stored diagnostics.
- An encryption error after a restart: restore the original key; do not reseed or delete data to hide the error.
curl --fail-with-body --max-time 15 http://127.0.0.1:3017/api/health8. Stop, resume, and update without losing data #
Press Ctrl-C in the app and worker terminals, then use docker compose stop postgres if you want to stop the database. Resume with docker compose up -d --wait postgres and restart both npm commands. Stopping containers preserves the named volume. docker compose down -v deletes it; do not use that as a routine fix.
Before updating a valued installation, back up the database and preserve encryption secrets. Read the release’s migration notes, stop the relevant processes, install the locked dependencies, run migrations, and restart. Never seed a production database. For production requirements, repository operators should read docs/deployment.md and docs/operations/infrastructure-setup-guide.md before selecting live mode.
Sources and further reading
Written for Stampwing with AI assistance and checked against the linked documentation. Examples are educational; simulated results are labelled. How these resources are maintained.