Self-Hosting
Deploy QA Studio on your own server with full control over your data. QA Studio uses SQLite for storage, so there is no external database to configure — just build, run, and persist the data directory.
Prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Node.js | 20 or higher | Required. LTS version recommended for production stability. |
| pnpm | 8 or higher | Required for building the project (workspace management). |
| Google Chrome | Latest stable | Optional. Only needed if tests use Real Browser mode (useRealChrome: true). |
QA Studio uses SQLite as its database, which is embedded in the server process. No external database server (PostgreSQL, MySQL, etc.) is needed. The entire database is a single file in the data directory.
Production Build
Build both the server and the dashboard for production:
# Install dependencies
pnpm install
# Install Playwright browsers
pnpm setup
# Build all packages
pnpm build
This produces two build artifacts:
| Package | Output | Description |
|---|---|---|
| Dashboard | apps/dashboard/dist/ |
Static HTML, CSS, and JavaScript files. Can be served by nginx, the Fastify server, or any static file host. |
| Server | apps/server/dist/ |
Compiled TypeScript to JavaScript. The entry point is apps/server/dist/server.js. |
Running in Production
Start the server with the NODE_ENV set to production:
NODE_ENV=production node apps/server/dist/server.js
The server starts on port 3001 by default and listens on all interfaces (0.0.0.0). On startup, it:
- Initializes (or opens) the SQLite database
- Runs any pending schema migrations
- Creates the
./data/directory if it does not exist - Registers all API routes and WebSocket support
- Starts the automatic cleanup schedule (daily at 3 AM)
- Loads all enabled scheduled runs via
loadAllSchedules()
Environment Variables
QA Studio supports the following environment variables for configuration:
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
The port the API server listens on. |
RETENTION_DAYS |
30 |
Number of days to keep run data before automatic cleanup. Set to 0 to disable cleanup. |
NODE_ENV |
development |
Set to production for production deployments. |
Data Directory
QA Studio stores all persistent data in the ./data/ directory relative to the current working directory. This directory is created automatically on first startup.
data/
├── qa-studio.db # SQLite database (all projects, tests, runs, etc.)
├── screenshots/ # Screenshot images from test runs
├── videos/ # Video recordings from test runs
└── diffs/ # Visual regression diff images
For data durability, you must persist this directory. If the data/ directory is lost, all projects, tests, run history, screenshots, and visual regression baselines are lost.
Back up the data/ directory regularly, especially the qa-studio.db file. This single SQLite file contains all your projects, tests, flows, run history, schedules, and baselines. Consider setting up automated daily backups to a remote location.
Backup Strategy
A simple backup approach for the SQLite database:
# Simple file copy (stop writes first for consistency, or use SQLite backup API)
cp data/qa-studio.db backups/qa-studio-$(date +%Y%m%d).db
# Or use SQLite's built-in backup command (safe even while server is running)
sqlite3 data/qa-studio.db ".backup backups/qa-studio-$(date +%Y%m%d).db"
For the screenshots, videos, and diffs directories, you can use rsync or any file backup tool to sync them to a backup location.
PM2 Process Manager
PM2 is a popular process manager for Node.js that provides automatic restarts, log management, and cluster mode. Here is an example configuration:
{
"apps": [
{
"name": "qa-studio",
"script": "apps/server/dist/server.js",
"cwd": "/opt/qa-studio",
"env": {
"NODE_ENV": "production",
"PORT": "3001",
"RETENTION_DAYS": "30"
},
"instances": 1,
"autorestart": true,
"watch": false,
"max_memory_restart": "1G",
"log_date_format": "YYYY-MM-DD HH:mm:ss"
}
]
}
# Start with PM2
pm2 start ecosystem.config.json
# Save the process list so it restarts after reboot
pm2 save
# Set up PM2 to start on system boot
pm2 startup
QA Studio uses SQLite, which only supports single-writer access. Do not use PM2 cluster mode (multiple instances). Always set instances to 1.
Systemd Service
For Linux servers, you can run QA Studio as a systemd service for automatic start on boot:
[Unit]
Description=QA Studio Test Automation Server
After=network.target
[Service]
Type=simple
User=qa-studio
WorkingDirectory=/opt/qa-studio
ExecStart=/usr/bin/node apps/server/dist/server.js
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=NODE_ENV=production
Environment=PORT=3001
Environment=RETENTION_DAYS=30
[Install]
WantedBy=multi-user.target
# Reload systemd to pick up the new service file
sudo systemctl daemon-reload
# Enable the service to start on boot
sudo systemctl enable qa-studio
# Start the service now
sudo systemctl start qa-studio
# Check the status
sudo systemctl status qa-studio
# View logs
sudo journalctl -u qa-studio -f
Nginx Reverse Proxy
In production, you typically run QA Studio behind a reverse proxy like nginx. This lets you serve the dashboard static files efficiently, terminate SSL, and route traffic to the server.
server {
listen 80;
server_name qa-studio.example.com;
# Serve the dashboard static files
location / {
root /opt/qa-studio/apps/dashboard/dist;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to the Fastify server
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
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;
# SSE support: disable buffering for streaming responses
proxy_buffering off;
proxy_cache off;
# WebSocket support (required for the recorder feature)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Increase timeout for long-running test executions
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
# Serve data files (screenshots, videos, diffs)
location /data/ {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
}
}
Key configuration details:
- Static files: The
location /block serves the built dashboard fromapps/dashboard/dist. Thetry_filesdirective with/index.htmlfallback ensures client-side routing works correctly. - API proxy: All requests to
/api/are forwarded to the Fastify server on port 3001. - SSE support:
proxy_buffering offis critical for Server-Sent Events (used for live run progress). Without this, nginx buffers the response and the client does not receive real-time updates. - WebSocket support: The
UpgradeandConnectionheaders are required for the recorder's WebSocket connection. Without these, the recorder feature will not work. - Timeout: Extended to 300 seconds to accommodate long-running test executions.
The recorder feature requires WebSocket connections. Make sure your reverse proxy is configured to pass Upgrade and Connection headers to the backend. Without this, the recorder will fail to establish a connection for streaming captured steps.
SSL with Let's Encrypt
For HTTPS, use Certbot to obtain a free SSL certificate:
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain and install certificate (modifies nginx config automatically)
sudo certbot --nginx -d qa-studio.example.com
# Certbot auto-renews via a systemd timer. Verify:
sudo systemctl status certbot.timer
Serving the Dashboard
There are two approaches to serving the dashboard frontend:
Option 1: Nginx (Recommended)
Serve the static files directly from nginx as shown in the configuration above. This is more efficient because nginx is optimized for static file serving and can handle gzip compression, caching headers, and high concurrency.
Option 2: Fastify Static Plugin
The Fastify server already serves the data/ directory as static files using @fastify/static. You could configure it to also serve the dashboard's dist/ directory. However, using nginx for static files is generally preferred for production deployments.
Deployment Checklist
Before going live, verify the following:
- Node.js 20+ is installed:
node --version - All dependencies installed:
pnpm install - Playwright browsers installed:
pnpm setup - Production build completed:
pnpm build NODE_ENV=productionis setPORTis configured (default: 3001)RETENTION_DAYSis set appropriately- The
data/directory is on persistent storage - A backup strategy is in place for
data/qa-studio.db - Process manager (PM2 or systemd) is configured for auto-restart
- Reverse proxy is configured with SSE and WebSocket support
- Health check endpoint is reachable:
curl http://localhost:3001/api/health
Troubleshooting
| Issue | Solution |
|---|---|
| Server won't start | Check Node.js version (node --version, must be 20+). Check logs with journalctl -u qa-studio or PM2 logs. |
| Tests fail to run (browser errors) | Ensure Playwright browsers are installed: npx playwright install. On Linux, install system dependencies: npx playwright install-deps. |
| Recorder WebSocket not connecting | Verify nginx passes Upgrade and Connection headers. Check that @fastify/websocket is registered. |
| Live run progress not streaming | Ensure proxy_buffering off is set in nginx for the /api/ location block. |
| Database locked errors | Ensure only one server instance is running. Do not use PM2 cluster mode. |
| Disk space growing | Check RETENTION_DAYS value. Trigger manual cleanup: curl -X POST http://localhost:3001/api/admin/cleanup. |
| Dashboard shows blank page | Verify apps/dashboard/dist/ exists and nginx try_files falls back to /index.html. |