Deployment Guide
This guide covers self-hosting PixelCI — the API/UI server, background workers, and required infrastructure.
Prerequisites
- MySQL 8+ — stores apps, builds, branches, screens, and user data
- Redis — job queue (BullMQ) and caching
- S3-compatible storage — screenshots and diff images (AWS S3, MinIO, SeaweedFS, etc.)
Docker Quick Start
1. Start the server
docker run -d \
--name pixelci \
-p 7924:7924 \
-e APP_ENV=production \
-e ENABLE_JOB_RUNNER=true \
-e AUTH_JWT_SECRET=your-secret-key \
-e REDIS_HOST=redis \
-e REDIS_PREFIX=pixelci \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD_SECRET=your-password \
-e MYSQL_DATABASE=pixelci \
-e S3_ENDPOINT=https://s3.amazonaws.com \
-e S3_REGION=us-east-1 \
-e S3_ACCESS_KEY_ID=your-access-key \
-e S3_ACCESS_SECRET=your-secret-key \
-e S3_BUCKET=pixelci \
ghcr.io/zyno-io/pixelci:latestSetting ENABLE_JOB_RUNNER=true makes the server also process build comparison jobs. For production deployments with higher throughput, you can run a separate worker process instead.
2. Run database migrations
docker exec pixelci node . migration:run3. Access the web interface
Visit http://localhost:7924. On first launch, the onboarding wizard will guide you through creating your first VCS integration and logging in.
Docker Compose
A complete setup with MySQL, Redis, and PixelCI:
services:
pixelci:
image: ghcr.io/zyno-io/pixelci:latest
ports:
- '7924:7924'
environment:
APP_ENV: production
ENABLE_JOB_RUNNER: 'true'
AUTH_JWT_SECRET: your-secret-key
REDIS_HOST: redis
REDIS_PREFIX: pixelci
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD_SECRET: your-password
MYSQL_DATABASE: pixelci
S3_ENDPOINT: https://s3.amazonaws.com
S3_REGION: us-east-1
S3_ACCESS_KEY_ID: your-access-key
S3_ACCESS_SECRET: your-secret-key
S3_BUCKET: pixelci
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: your-password
MYSQL_DATABASE: pixelci
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-pyour-password']
interval: 5s
timeout: 3s
retries: 10
redis:
image: redis:7-alpine
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 10
volumes:
mysql-data:After starting:
docker compose up -d
docker compose exec pixelci node . migration:runThen visit http://localhost:7924. The onboarding wizard will guide you through initial setup.
Running Migrations
Migrations must be run whenever you deploy a new version:
# Docker
docker exec pixelci node . migration:run
# Docker Compose
docker compose exec pixelci node . migration:run
# Kubernetes
kubectl exec deploy/pixelci-server -- node . migration:runEnvironment Variable Reference
General
| Variable | Required | Default | Description |
|---|---|---|---|
APP_ENV | Yes | — | Environment name (use production for deployed instances) |
Authentication
| Variable | Required | Default | Description |
|---|---|---|---|
AUTH_JWT_SECRET | Yes | — | Secret key for JWT token signing |
MySQL
| Variable | Required | Default | Description |
|---|---|---|---|
MYSQL_HOST | Yes | — | MySQL server hostname |
MYSQL_PORT | No | 3306 | MySQL server port |
MYSQL_USER | Yes | — | MySQL username |
MYSQL_PASSWORD_SECRET | Yes | — | MySQL password |
MYSQL_DATABASE | Yes | — | MySQL database name |
Redis
| Variable | Required | Default | Description |
|---|---|---|---|
REDIS_HOST | Yes* | — | Redis server hostname |
REDIS_PORT | No | 6379 | Redis server port |
REDIS_PREFIX | No | package name | Prefix for all Redis keys (recommended: pixelci) |
REDIS_SENTINEL_HOST | Yes* | — | Redis Sentinel hostname (alternative to REDIS_HOST) |
REDIS_SENTINEL_PORT | No | 26379 | Redis Sentinel port |
REDIS_SENTINEL_NAME | Yes* | — | Redis Sentinel master name (required with sentinel) |
* Provide either REDIS_HOST or REDIS_SENTINEL_HOST + REDIS_SENTINEL_NAME.
S3 Storage
| Variable | Required | Default | Description |
|---|---|---|---|
S3_ENDPOINT | Yes | — | S3 endpoint URL |
S3_REGION | Yes | — | S3 region |
S3_ACCESS_KEY_ID | Yes | — | S3 access key |
S3_ACCESS_SECRET | Yes | — | S3 secret key |
S3_BUCKET | Yes | — | S3 bucket name |
Workers
| Variable | Required | Default | Description |
|---|---|---|---|
ENABLE_JOB_RUNNER | No | false | Enable the job runner on the server process |
Tuning
| Variable | Required | Default | Description |
|---|---|---|---|
DEFAULT_PIXEL_MATCH_THRESHOLD | No | 0.2 | Per-pixel color sensitivity for comparison (0–1, smaller = more sensitive). Higher values reduce false positives from sub-pixel text rendering and anti-aliasing differences. |
DEFAULT_PIXEL_DIFF_PCT_THRESHOLD | No | 1 | Percentage of total pixels that must differ to flag a screen as changed (e.g. 0.5 for 0.5%) |
Architecture Overview
PixelCI uses a single Docker image (ghcr.io/zyno-io/pixelci) for all components. Build comparison jobs are processed by a worker, which can run either within the server process or as a separate process.
Single-process mode
Set ENABLE_JOB_RUNNER=true on the server. The server handles both HTTP requests and background jobs. This is the simplest setup and works well for most teams.
Separate worker process
For higher throughput or to isolate job processing from the API, run the worker as a separate container using the same image with a different command:
docker run -d \
--name pixelci-worker \
-e APP_ENV=production \
-e AUTH_JWT_SECRET=your-secret-key \
-e REDIS_HOST=redis \
-e REDIS_PREFIX=pixelci \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD_SECRET=your-password \
-e MYSQL_DATABASE=pixelci \
-e S3_ENDPOINT=https://s3.amazonaws.com \
-e S3_REGION=us-east-1 \
-e S3_ACCESS_KEY_ID=your-access-key \
-e S3_ACCESS_SECRET=your-secret-key \
-e S3_BUCKET=pixelci \
ghcr.io/zyno-io/pixelci:latest \
node . worker:startWhen using a separate worker, do not set ENABLE_JOB_RUNNER on the server.
Kubernetes
For Kubernetes deployments, you can either:
- Single-process: One Deployment with
ENABLE_JOB_RUNNER=true, exposed via a Service/Ingress on port 7924 - Separate worker: Two Deployments sharing the same image — the server (default command) and a worker (override command to
node . worker:start)
Run migrations via kubectl exec against the server deployment after each upgrade:
kubectl exec deploy/pixelci-server -- node . migration:runAll deployments share the same environment variables. Use Kubernetes Secrets for sensitive values (AUTH_JWT_SECRET, MYSQL_PASSWORD_SECRET, S3 credentials).