Install without Docker

Install self-hosted Quackback without Docker using Bun 1.4+ and PostgreSQL 14+ with pgvector and pg_trgm, run it under systemd or PM2, and keep it updated.

JM
James Morton
Written By James MortonLast updated 1 day ago

Install self-hosted Quackback directly on a server with Bun and PostgreSQL, without Docker. Use this if you prefer to run the app without containers.

Prerequisites

Bun

Version 1.4.0 or higher.

# Install Bun
curl -fsSL https://bun.sh/install | bash

# Verify installation
bun --version

PostgreSQL

Version 14 or higher, with these extensions:

  • vector (pgvector 0.5.0 or newer) for similarity search
  • pg_trgm (from the PostgreSQL contrib package) for text search

PostgreSQL is the only datastore you need. Background jobs, caches, and realtime events all run on it, so there is no Redis or queue service to install, and pg_cron is not required. See System requirements for connection-mode and privilege requirements.

Other tools

  • Git
  • OpenSSL (for generating secrets)

Installation steps

1. Clone the repository

git clone https://github.com/quackbackio/quackback.git
cd quackback

2. Install dependencies

bun install

3. Configure the environment

Copy the example environment file and generate a secret:

cp .env.example .env
openssl rand -base64 32

Edit .env. Do not wrap values in quotes.

# Required settings
DATABASE_URL=postgresql://quackback:your_password@localhost:5432/quackback
SECRET_KEY=your-generated-secret-key-at-least-32-characters
BASE_URL=https://feedback.yourcompany.com

# One reverse proxy in front of the app (see the reverse proxy guide)
TRUSTED_PROXY_HOPS=1

See Environment variables for all options, and Configure email delivery to set up outbound email.

4. Set up PostgreSQL

Install PostgreSQL

Any version from 14 up works. These examples install PostgreSQL 18.

Ubuntu or Debian:

# Add the PostgreSQL APT repository
sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -

# Install PostgreSQL 18 and pgvector
sudo apt update
sudo apt install postgresql-18 postgresql-18-pgvector

macOS (Homebrew):

brew install postgresql@18
brew services start postgresql@18
brew install pgvector

RHEL, CentOS, or Fedora:

# Install the PostgreSQL repo
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm

# Install PostgreSQL and extensions
sudo dnf install -y postgresql18-server postgresql18-contrib pgvector_18

Create the database and user

sudo -u postgres psql
-- Create user
CREATE USER quackback WITH PASSWORD 'your_secure_password';

-- Create database
CREATE DATABASE quackback OWNER quackback;

-- Connect to database
\c quackback

-- Enable required extensions (Quackback also tries this on startup)
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- Grant permissions
GRANT ALL PRIVILEGES ON DATABASE quackback TO quackback;
GRANT ALL ON SCHEMA public TO quackback;

5. Run migrations

bun run db:migrate

This creates all tables and indexes. Before it changes anything, the migrator checks the PostgreSQL version, the extensions, and the database user's privileges, and stops with a clear message if something is missing.

6. Build the application

bun run build

This creates a production build in apps/web/.output/.

7. Start the server

bun run --cwd apps/web start

The server starts on port 3000 by default.

Process management

Warning: For production, use a process manager to keep Quackback running and restart it automatically on failures.

Create /etc/systemd/system/quackback.service:

[Unit]
Description=Quackback Feedback Platform
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=quackback
Group=quackback
WorkingDirectory=/opt/quackback
Environment=NODE_ENV=production
EnvironmentFile=/opt/quackback/.env
ExecStart=/home/quackback/.bun/bin/bun run --cwd apps/web start
Restart=always
RestartSec=10

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/quackback

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=quackback

[Install]
WantedBy=multi-user.target

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable quackback
sudo systemctl start quackback
sudo systemctl status quackback

# View logs
journalctl -u quackback -f

Tip: One instance handles everything by default (QUACKBACK_ROLE=all, the default when the variable is unset). To scale HTTP and background processing separately, add a second systemd unit with Environment=QUACKBACK_ROLE=worker and set the first unit's QUACKBACK_ROLE=web. See Scale with multiple replicas.

PM2 (alternative)

# Install PM2
bun install -g pm2

# Create an ecosystem file
cat > ecosystem.config.js << 'EOF'
module.exports = {
  apps: [{
    name: 'quackback',
    cwd: '/opt/quackback/apps/web',
    script: '.output/server/index.mjs',
    interpreter: 'bun',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G'
  }]
}
EOF

pm2 start ecosystem.config.js
pm2 save
pm2 startup

Directory structure

Recommended production layout:

/opt/quackback/
  .env                 Environment configuration
  apps/web/.output/    Built application
  packages/            Internal packages
  node_modules/        Dependencies
  package.json
  bun.lock

Security considerations

File permissions

# Create a dedicated user
sudo useradd -r -s /bin/false quackback

# Set ownership
sudo chown -R quackback:quackback /opt/quackback

# Restrict the .env file
sudo chmod 600 /opt/quackback/.env

Firewall

# UFW example
sudo ufw allow 22/tcp      # SSH
sudo ufw allow 80/tcp      # HTTP (for redirect)
sudo ufw allow 443/tcp     # HTTPS
sudo ufw enable

Warning: Do not expose port 3000 directly. Put a reverse proxy in front of it.

Update Quackback

Warning: Upgrading from 0.13? Read Upgrade from 0.13 to 0.14 first. It has one-time steps.

1. Stop the service

sudo systemctl stop quackback

2. Back up the database

pg_dump -Fc quackback > quackback_backup_$(date +%Y%m%d).dump

3. Pull updates

cd /opt/quackback
git pull origin main

4. Install dependencies

bun install

5. Run migrations

bun run db:migrate

6. Rebuild

bun run build

7. Restart the service

sudo systemctl start quackback

Tip: To track releases instead of the latest code, check out a release tag (for example git checkout v0.14.0) rather than pulling main.

Update script

Save this as /opt/quackback/update.sh to automate the process:

#!/bin/bash
set -e

cd /opt/quackback

echo "Stopping service..."
sudo systemctl stop quackback

echo "Creating backup..."
pg_dump -Fc quackback > /var/backups/quackback/quackback_$(date +%Y%m%d_%H%M%S).dump

echo "Pulling updates..."
git pull origin main

echo "Installing dependencies..."
bun install

echo "Running migrations..."
bun run db:migrate

echo "Building..."
bun run build

echo "Starting service..."
sudo systemctl start quackback

echo "Update complete!"

Health checks

# Liveness: the process is up
curl http://localhost:3000/api/health/live

# Readiness: database, migrations, and background workers
curl http://localhost:3000/api/health/ready

# Database connection
psql $DATABASE_URL -c "SELECT 1"

# Service status
sudo systemctl status quackback

Troubleshooting

Port already in use

# Find what's using port 3000
sudo lsof -i :3000

Stop that process, or change PORT in .env.

Database connection failed

Check the connection string format (postgresql://user:password@host:port/database) and test it:

psql "postgresql://quackback:password@localhost:5432/quackback" -c "SELECT 1"

Permission denied

Make sure the quackback user owns all files:

sudo chown -R quackback:quackback /opt/quackback

Build fails

Clear the cache and reinstall:

rm -rf node_modules
rm -rf apps/web/.output
bun install
bun run build

Migrations fail

Check the database user's permissions:

GRANT ALL PRIVILEGES ON DATABASE quackback TO quackback;
GRANT ALL ON SCHEMA public TO quackback;

Was this helpful?

Your feedback shapes what we write next.