Persistence
Persistence
How Pvdify stores state and manages data.
Overview
Pvdify separates:
- Control plane state — Managed by pvdifyd on VPS
- Application data — External databases (apps are stateless)
Control Plane Persistence
State Directory
/var/lib/pvdify/├── apps/ # App slot definitions│ ├── myapp.yaml│ ├── myapp-staging.yaml│ └── myapp-pr-123.yaml├── releases/ # Release history│ └── myapp/│ ├── v42.yaml│ ├── v41.yaml│ └── v40.yaml├── config/ # Encrypted config versions│ └── myapp/│ ├── v3.sops.yaml│ ├── v2.sops.yaml│ └── v1.sops.yaml├── tunnels/ # Tunnel configurations│ └── pvdify-apps.yml└── pvdifyd.db # SQLite for fast queriesSQLite Database
pvdifyd uses SQLite for fast queries and indexing:
-- Apps tableCREATE TABLE apps ( name TEXT PRIMARY KEY, environment TEXT NOT NULL, status TEXT NOT NULL, image TEXT, bind_port INTEGER, created_at DATETIME, updated_at DATETIME);
-- Releases tableCREATE TABLE releases ( id INTEGER PRIMARY KEY, app_name TEXT NOT NULL, version INTEGER NOT NULL, image TEXT NOT NULL, config_version TEXT, status TEXT NOT NULL, created_at DATETIME, created_by TEXT, UNIQUE(app_name, version));
-- Domains tableCREATE TABLE domains ( domain TEXT PRIMARY KEY, app_name TEXT NOT NULL, status TEXT NOT NULL, cf_record_id TEXT, created_at DATETIME);YAML Files
Human-readable state for debugging and backup:
name: myappenvironment: productionstatus: runningimage: ghcr.io/org/app:v1.2.3bind_port: 3001processes: web: command: npm start count: 2 worker: command: npm run worker count: 1domains: - myapp.com - www.myapp.comresources: memory: 512M cpu: 0.5healthcheck: path: /health interval: 30screated_at: 2026-01-01T10:00:00Zupdated_at: 2026-01-05T10:30:00ZSecrets (SOPS)
Config secrets encrypted with SOPS:
NODE_ENV: productionDATABASE_URL: postgres://...API_KEY: ENC[AES256_GCM,data:...,iv:...,tag:...]sops: kms: [] age: - recipient: age1... enc: | -----BEGIN AGE ENCRYPTED FILE----- ... -----END AGE ENCRYPTED FILE----- lastmodified: "2026-01-05T10:30:00Z" mac: ENC[...] version: 3.8.1Backup Strategy
Automated Backups
# Daily backup of /var/lib/pvdify//root/claude_backups/scripts/backup_pvdify.sh
# Contents:tar -czf /root/claude_backups/pvdify/pvdify-$(date +%Y%m%d).tar.gz \ /var/lib/pvdify/
# Retention: 7 daily, 4 weeklyPre-Change Snapshot
Before any system changes:
/root/claude_backups/create_snapshot.sh# Includes: /var/lib/pvdify/, systemd units, tunnel configDisaster Recovery
- Restore
/var/lib/pvdify/from backup - Rebuild SQLite index:
pvdifyd rebuild-index - Recreate systemd units:
pvdifyd regenerate-units - Verify tunnel config:
cloudflared tunnel ingress validate
Application Data
Apps are stateless by design. Data stored externally:
| Data Type | Recommended Service |
|---|---|
| PostgreSQL | PlanetScale, Supabase, Neon |
| MySQL | PlanetScale |
| Redis | Upstash, Redis Cloud |
| Files/Blobs | AWS S3 (pvd-cdn-assets) |
| Search | Algolia, Typesense |
| Analytics | PostHog (data.philoveracity.com) |
Database Connections
Apps receive connection strings via config:
pvdify config:set myapp \ DATABASE_URL=postgres://user:pass@db.example.com/myapp \ REDIS_URL=redis://user:pass@redis.example.com:6379File Storage
Use S3 for user uploads and assets:
pvdify config:set myapp \ AWS_ACCESS_KEY_ID=AKIA... \ AWS_SECRET_ACCESS_KEY=... \ S3_BUCKET=pvd-cdn-assetsSystemd Units
Process state managed by systemd:
/etc/systemd/system/├── pvdifyd.service # Control plane daemon├── pvdify-myapp-web@.service # App web process template├── pvdify-myapp-worker@.service # App worker template└── cloudflared-pvdify.service # Tunnel serviceUnit Template
# /etc/systemd/system/pvdify-myapp-web@.service[Unit]Description=Pvdify myapp web process %iAfter=network.target
[Service]Type=simpleUser=pvdifyExecStart=/usr/bin/podman run --rm \ --name pvdify-myapp-web-%i \ -p 3001:3000 \ --env-file /var/lib/pvdify/config/myapp/current.env \ ghcr.io/org/app:v1.2.3 \ npm startRestart=alwaysRestartSec=5
[Install]WantedBy=multi-user.targetPodman Storage
Container images and layers:
/var/lib/containers/storage/├── overlay/ # Image layers├── overlay-images/ # Image metadata└── overlay-containers/ # Container stateImage Cleanup
Automated cleanup of old images:
# Weekly cronpodman image prune -a --filter "until=168h"Logging
systemd Journal
All logs aggregated in journald:
# pvdifyd logsjournalctl -u pvdifyd -f
# App logsjournalctl -u pvdify-myapp-web@1 -f
# All pvdify logsjournalctl -u 'pvdify-*' -fLog Retention
[Journal]SystemMaxUse=2GMaxRetentionSec=7dayMetrics
Basic metrics via systemd and podman:
# Process statussystemctl status pvdify-myapp-web@1
# Container statspodman stats pvdify-myapp-web-1
# Resource usagepodman pod statsFuture: Prometheus metrics endpoint in pvdifyd.
State Diagram
┌─────────────────────────────────────────────────────────────┐│ pvdifyd ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ ││ │ SQLite │ │ YAML │ │ SOPS (secrets) │ ││ │ (index) │ │ (state) │ │ (encrypted) │ ││ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ ││ │ │ │ ││ └────────────────┼─────────────────────┘ ││ │ ││ ▼ ││ /var/lib/pvdify/ │└─────────────────────────────────────────────────────────────┘ │ ┌────────────────┼────────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ systemd │ │ Podman │ │cloudflared│ │ units │ │ images │ │ tunnel │ └──────────┘ └──────────┘ └──────────┘