sha256:3d71aab3eabad7064998fd2b39803b6d3054daec956434ce76899e466a4c470c
Last pushed
18 days by kahmyl
Type
Compose
Manifest digest
sha256:3d71aab3eabad7064998fd2b39803b6d3054daec956434ce76899e466a4c470c
name: scry
x-api-environment: &api-environment
NODE_ENV: production
DATABASE_URL: postgres://scry:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@postgres:5432/${SCRY_DATABASE_NAME:-scry_browser_v2}
REDIS_URL: redis://redis:6379
BROWSER_V2_ARTIFACT_ROOT: /workspace/apps/api/artifacts/browser-v2
SUPABASE_URL: ${SUPABASE_URL:?Set SUPABASE_URL}
SCRY_SERVICE_TOKEN: ${SCRY_SERVICE_TOKEN:?Set SCRY_SERVICE_TOKEN}
BROWSER_V2_ENABLED: "true"
BROWSER_V2_RENDER_VIDEO: "true"
BROWSER_V2_REQUIRE_ACTIVATED_CUTOVER: ${BROWSER_V2_REQUIRE_ACTIVATED_CUTOVER:-true}
BROWSER_V2_PROTECTED_KEYS_JSON: ${BROWSER_V2_PROTECTED_KEYS_JSON:?Load the versioned browser keyring from the production secret manager}
BROWSER_V2_PROTECTED_KEY_VERSION: ${BROWSER_V2_PROTECTED_KEY_VERSION:?Set the active browser key version}
x-backup-environment: &backup-environment
RESTIC_REPOSITORY: ${RESTIC_REPOSITORY:-}
RESTIC_PASSWORD: ${RESTIC_PASSWORD:-}
AWS_ACCESS_KEY_ID: ${BACKUP_AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${BACKUP_AWS_SECRET_ACCESS_KEY:-}
AWS_DEFAULT_REGION: ${BACKUP_AWS_REGION:-}
AWS_ENDPOINT: ${BACKUP_AWS_ENDPOINT:-}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
PGHOST: postgres
PGUSER: scry
BACKUP_KEEP_HOURLY: ${BACKUP_KEEP_HOURLY:-48}
BACKUP_KEEP_DAILY: ${BACKUP_KEEP_DAILY:-30}
BACKUP_KEEP_WEEKLY: ${BACKUP_KEEP_WEEKLY:-12}
BACKUP_KEEP_MONTHLY: ${BACKUP_KEEP_MONTHLY:-12}
RESTORE_DRILL_RTO_SECONDS: ${RESTORE_DRILL_RTO_SECONDS:-3600}
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
environment:
SCRY_DOMAIN: ${SCRY_DOMAIN:?Set SCRY_DOMAIN}
ports:
- "80:80"
- "443:443"
- "443:443/udp"
configs:
- source: caddyfile
target: /etc/caddy/Caddyfile
volumes:
- caddy_data:/data
- caddy_config:/config
depends_on:
web:
condition: service_healthy
api:
condition: service_healthy
mcp:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2019/config/"]
start_period: 10s
start_interval: 1s
interval: 5s
timeout: 3s
retries: 20
web:
image: ${SCRY_IMAGE_REF:?Set SCRY_IMAGE_REF to an immutable image digest}
restart: unless-stopped
command: ["node", "apps/web/scripts/serve-dist.mjs"]
environment:
WEB_PORT: "3000"
SCRY_PUBLIC_API_BASE_URL: /api
SCRY_PUBLIC_MCP_SERVER_URL: /mcp
SUPABASE_URL: ${SUPABASE_URL:?Set SUPABASE_URL}
SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_PUBLISHABLE_KEY:?Set SUPABASE_PUBLISHABLE_KEY}
SCRY_RELEASE_ID: ${SCRY_RELEASE_ID:?Set SCRY_RELEASE_ID}
SCRY_SCHEMA_FINGERPRINT: ${SCRY_SCHEMA_FINGERPRINT:?Set SCRY_SCHEMA_FINGERPRINT}
depends_on:
api:
condition: service_started
mcp:
condition: service_started
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/"]
start_period: 10s
start_interval: 1s
interval: 5s
timeout: 3s
retries: 20
api:
image: ${SCRY_IMAGE_REF:?Set SCRY_IMAGE_REF to an immutable image digest}
restart: unless-stopped
environment:
<<: *api-environment
API_HOST: 0.0.0.0
API_PORT: "4000"
API_CORS_ORIGINS: https://${SCRY_DOMAIN:?Set SCRY_DOMAIN}
SCRY_RELEASE_ID: ${SCRY_RELEASE_ID:?Set SCRY_RELEASE_ID}
SCRY_SCHEMA_FINGERPRINT: ${SCRY_SCHEMA_FINGERPRINT:?Set SCRY_SCHEMA_FINGERPRINT}
command:
[
"apps/api/node_modules/.bin/tsx",
"--tsconfig",
"apps/api/tsconfig.json",
"apps/api/src/browser-v2-main.ts",
]
depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- artifacts:/workspace/apps/api/artifacts/browser-v2
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4000/api/v2/health"]
start_period: 20s
start_interval: 1s
interval: 5s
timeout: 3s
retries: 20
worker:
image: ${SCRY_IMAGE_REF:?Set SCRY_IMAGE_REF to an immutable image digest}
restart: unless-stopped
command:
[
"apps/api/node_modules/.bin/tsx",
"--tsconfig",
"apps/api/tsconfig.json",
"apps/api/src/browser-v2-worker.ts",
]
environment:
<<: *api-environment
SCRY_BROWSER_CHANNEL: chromium
BROWSER_V2_SESSION_CONCURRENCY: ${SCRY_WORKER_CONCURRENCY:-4}
BROWSER_V2_COMMAND_CONCURRENCY: ${SCRY_COMMAND_CONCURRENCY:-8}
SCRY_RELEASE_ID: ${SCRY_RELEASE_ID:?Set SCRY_RELEASE_ID}
SCRY_SCHEMA_FINGERPRINT: ${SCRY_SCHEMA_FINGERPRINT:?Set SCRY_SCHEMA_FINGERPRINT}
depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- artifacts:/workspace/apps/api/artifacts/browser-v2
shm_size: 1gb
stop_grace_period: 30s
migrate:
image: ${SCRY_IMAGE_REF:?Set SCRY_IMAGE_REF to an immutable image digest}
restart: "no"
command:
[
"apps/api/node_modules/.bin/tsx",
"--tsconfig",
"apps/api/tsconfig.json",
"apps/api/scripts/migrate-browser-v2.ts",
]
environment:
BROWSER_V2_DATABASE_URL: postgres://scry:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@postgres:5432/${SCRY_DATABASE_NAME:-scry_browser_v2}
BROWSER_V2_EXPECTED_DATABASE: ${SCRY_DATABASE_NAME:-scry_browser_v2}
BROWSER_V2_SCHEMA_CONFIRM: CREATE_EMPTY_V2
depends_on:
postgres:
condition: service_healthy
mcp:
image: ${SCRY_IMAGE_REF:?Set SCRY_IMAGE_REF to an immutable image digest}
restart: unless-stopped
command:
[
"apps/mcp/node_modules/.bin/tsx",
"--tsconfig",
"apps/mcp/tsconfig.json",
"apps/mcp/src/browser-v2-http.ts",
]
environment:
MCP_HOST: 0.0.0.0
MCP_PORT: "4100"
MCP_ALLOWED_ORIGINS: https://${SCRY_DOMAIN:?Set SCRY_DOMAIN}
SCRY_API_BASE_URL: http://api:4000/api
SCRY_PUBLIC_API_BASE_URL: https://${SCRY_DOMAIN:?Set SCRY_DOMAIN}/api
SCRY_SERVICE_TOKEN: ${SCRY_SERVICE_TOKEN:?Set SCRY_SERVICE_TOKEN}
SCRY_RELEASE_ID: ${SCRY_RELEASE_ID:?Set SCRY_RELEASE_ID}
SCRY_SCHEMA_FINGERPRINT: ${SCRY_SCHEMA_FINGERPRINT:?Set SCRY_SCHEMA_FINGERPRINT}
depends_on:
api:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4100/health"]
start_period: 20s
start_interval: 1s
interval: 5s
timeout: 3s
retries: 20
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${SCRY_DATABASE_NAME:-scry_browser_v2}
POSTGRES_USER: scry
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- postgres_wal_archive:/wal-archive
command:
- postgres
- -c
- wal_level=replica
- -c
- archive_mode=${POSTGRES_ARCHIVE_MODE:-off}
- -c
- archive_timeout=${POSTGRES_ARCHIVE_TIMEOUT:-300s}
- -c
- hba_file=/etc/postgresql/pg_hba.conf
- -c
- archive_command=test -f /wal-archive/%f || (cp %p /wal-archive/.%f.tmp && chmod 0600 /wal-archive/.%f.tmp && mv /wal-archive/.%f.tmp /wal-archive/%f)
configs:
- source: postgres_hba
target: /etc/postgresql/pg_hba.conf
healthcheck:
test: ["CMD-SHELL", "pg_isready -U scry -d ${SCRY_DATABASE_NAME:-scry_browser_v2}"]
start_period: 10s
start_interval: 1s
interval: 5s
timeout: 5s
retries: 10
depends_on:
postgres-storage-init:
condition: service_completed_successfully
postgres-storage-init:
image: postgres:17-alpine
restart: "no"
user: root
command: ["sh", "-c", "chown -R postgres:postgres /wal-archive && chmod 0700 /wal-archive"]
volumes:
- postgres_wal_archive:/wal-archive
redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes", "--save", "60", "1"]
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
start_period: 10s
start_interval: 1s
interval: 5s
timeout: 3s
retries: 10
postgres-base-backup:
profiles: [operations]
image: ${SCRY_BACKUP_IMAGE_REF:-scry-backup:local}
restart: unless-stopped
command: ["postgres-base"]
environment:
<<: *backup-environment
POSTGRES_BASE_BACKUP_INTERVAL_SECONDS: ${POSTGRES_BASE_BACKUP_INTERVAL_SECONDS:-86400}
volumes:
- backup_staging:/staging
- backup_metrics:/metrics
depends_on:
backup-init:
condition: service_completed_successfully
postgres:
condition: service_healthy
postgres-wal-backup:
profiles: [operations]
image: ${SCRY_BACKUP_IMAGE_REF:-scry-backup:local}
restart: unless-stopped
command: ["postgres-wal"]
environment:
<<: *backup-environment
POSTGRES_WAL_BACKUP_INTERVAL_SECONDS: ${POSTGRES_WAL_BACKUP_INTERVAL_SECONDS:-300}
volumes:
- postgres_wal_archive:/wal-archive:ro
- backup_metrics:/metrics
depends_on:
backup-init:
condition: service_completed_successfully
postgres:
condition: service_healthy
artifact-backup:
profiles: [operations]
image: ${SCRY_BACKUP_IMAGE_REF:-scry-backup:local}
restart: unless-stopped
command: ["artifacts"]
environment:
<<: *backup-environment
ARTIFACT_BACKUP_INTERVAL_SECONDS: ${ARTIFACT_BACKUP_INTERVAL_SECONDS:-900}
volumes:
- artifacts:/artifacts:ro
- backup_metrics:/metrics
depends_on:
backup-init:
condition: service_completed_successfully
backup-maintenance:
profiles: [operations]
image: ${SCRY_BACKUP_IMAGE_REF:-scry-backup:local}
restart: unless-stopped
command: ["maintenance"]
environment:
<<: *backup-environment
BACKUP_MAINTENANCE_INTERVAL_SECONDS: ${BACKUP_MAINTENANCE_INTERVAL_SECONDS:-86400}
BACKUP_CHECK_DATA_SUBSET: ${BACKUP_CHECK_DATA_SUBSET:-5%}
volumes:
- backup_metrics:/metrics
depends_on:
backup-init:
condition: service_completed_successfully
restore-drill:
profiles: [operations]
image: ${SCRY_BACKUP_IMAGE_REF:-scry-backup:local}
restart: unless-stopped
command: ["restore-drill"]
environment:
<<: *backup-environment
RESTORE_DRILL_INTERVAL_SECONDS: ${RESTORE_DRILL_INTERVAL_SECONDS:-604800}
volumes:
- restore_drill:/restore-drill
- backup_metrics:/metrics
depends_on:
backup-init:
condition: service_completed_successfully
backup-init:
profiles: [operations]
image: ${SCRY_BACKUP_IMAGE_REF:-scry-backup:local}
restart: "no"
command: ["init"]
environment:
<<: *backup-environment
postgres-exporter:
profiles: [operations]
image: quay.io/prometheuscommunity/postgres-exporter:v0.17.1
restart: unless-stopped
environment:
DATA_SOURCE_NAME: postgresql://scry:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@postgres:5432/${SCRY_DATABASE_NAME:-scry_browser_v2}?sslmode=disable
depends_on:
postgres:
condition: service_healthy
redis-exporter:
profiles: [operations]
image: oliver006/redis_exporter:v1.67.0
restart: unless-stopped
environment:
REDIS_ADDR: redis://redis:6379
depends_on:
redis:
condition: service_healthy
blackbox-exporter:
profiles: [operations]
image: prom/blackbox-exporter:v0.27.0
restart: unless-stopped
command: ["--config.file=/etc/blackbox/blackbox.yml"]
configs:
- source: blackbox_config
target: /etc/blackbox/blackbox.yml
backup-metrics:
profiles: [operations]
image: prom/node-exporter:v1.9.1
restart: unless-stopped
command:
[
"--collector.disable-defaults",
"--collector.textfile",
"--collector.textfile.directory=/metrics",
]
volumes:
- backup_metrics:/metrics:ro
alertmanager:
profiles: [operations]
image: prom/alertmanager:v0.28.1
restart: unless-stopped
command: ["--config.file=/etc/alertmanager/alertmanager.yml", "--storage.path=/alertmanager"]
configs:
- source: alertmanager_config
target: /etc/alertmanager/alertmanager.yml
volumes:
- alertmanager_data:/alertmanager
prometheus:
profiles: [operations]
image: prom/prometheus:v3.5.0
restart: unless-stopped
command:
[
"--config.file=/etc/prometheus/prometheus.yml",
"--storage.tsdb.path=/prometheus",
"--storage.tsdb.retention.time=30d",
]
configs:
- source: prometheus_config
target: /etc/prometheus/prometheus.yml
- source: prometheus_rules
target: /etc/prometheus/rules.yml
volumes:
- prometheus_data:/prometheus
depends_on:
- alertmanager
- postgres-exporter
- redis-exporter
- blackbox-exporter
- backup-metrics
volumes:
postgres_data:
redis_data:
artifacts:
postgres_wal_archive:
backup_staging:
backup_metrics:
restore_drill:
prometheus_data:
alertmanager_data:
caddy_data:
caddy_config:
configs:
postgres_hba:
content: |
# Unix-socket access stays inside the PostgreSQL container.
local all all trust
# Every network client, including backup clients, must authenticate.
host all all 0.0.0.0/0 scram-sha-256
host all all ::/0 scram-sha-256
host replication all 0.0.0.0/0 scram-sha-256
host replication all ::/0 scram-sha-256
prometheus_config:
content: |
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- /etc/prometheus/rules.yml
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["prometheus:9090"]
- job_name: postgres
static_configs:
- targets: ["postgres-exporter:9187"]
- job_name: redis
static_configs:
- targets: ["redis-exporter:9121"]
- job_name: backup
static_configs:
- targets: ["backup-metrics:9100"]
- job_name: scry-blackbox
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- http://api:4000/api/ready
- http://mcp:4100/health
- http://web:3000/health
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
prometheus_rules:
content: |
groups:
- name: scry-production
rules:
- alert: ScryEndpointUnavailable
expr: probe_success{job="scry-blackbox"} == 0
for: 2m
labels: { severity: critical }
annotations:
summary: "Scry endpoint unavailable: {{ $$labels.instance }}"
- alert: PostgreSQLUnavailable
expr: pg_up == 0
for: 1m
labels: { severity: critical }
annotations: { summary: "PostgreSQL exporter cannot reach PostgreSQL" }
- alert: RedisUnavailable
expr: redis_up == 0
for: 1m
labels: { severity: critical }
annotations: { summary: "Redis exporter cannot reach Redis" }
- alert: BackupJobFailed
expr: scry_backup_last_run_status == 0
for: 5m
labels: { severity: critical }
annotations: { summary: "Backup or restore operation failed: {{ $$labels.job }}" }
- alert: PostgreSQLWalBackupStale
expr: time() - scry_backup_last_success_unixtime{job="postgres-wal"} > 600
for: 2m
labels: { severity: critical }
annotations:
{ summary: "PostgreSQL WAL off-host backup exceeds the 10-minute alert threshold" }
- alert: ArtifactBackupStale
expr: time() - scry_backup_last_success_unixtime{job="artifacts"} > 1800
for: 5m
labels: { severity: warning }
annotations: { summary: "Artifact backup is older than 30 minutes" }
- alert: RestoreDrillStale
expr: time() - scry_backup_last_success_unixtime{job="restore-drill"} > 691200
for: 1h
labels: { severity: critical }
annotations: { summary: "No successful restore drill in eight days" }
blackbox_config:
content: |
modules:
http_2xx:
prober: http
timeout: 5s
http:
method: GET
preferred_ip_protocol: ip4
alertmanager_config:
content: |
route:
receiver: operations
group_by: [alertname, severity]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receivers:
- name: operations
webhook_configs:
- url: ${SCRY_ALERT_WEBHOOK_URL:-http://127.0.0.1:65535/operations-disabled}
send_resolved: true
caddyfile:
content: |
${SCRY_DOMAIN:?Set SCRY_DOMAIN} {
handle /api/* {
reverse_proxy api:4000
}
handle /mcp {
reverse_proxy mcp:4100
}
handle {
reverse_proxy web:3000
}
encode zstd gzip
}
docker compose -f oci://kahmyl/scry-stack:latest upUse the above command to pull and run the Compose file. Learn more.
The PostgreSQL object-relational database system provides reliability and data integrity.
Pulls
1B+
Stars
15023
Last Updated
2 days
Redis is the world’s fastest data platform for caching, vector search, and NoSQL databases.
Pulls
1B+
Stars
13619
Last Updated
2 days
Caddy 2 is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go.
Pulls
500M+
Stars
988
Last Updated
1 day
Prometheus Exporter for Redis Metrics. Supports Redis 2.x, 3.x, 4.x and 5.x
Pulls
100M+
Stars
54
Last Updated
4 days
prometheuscommunity/postgres-exporter
Pulls
5M+
Stars
19
Last Updated
4 days