Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Production deployment for PocketBase. Use when deploying PocketBase to a server, setting up Docker, configuring systemd, reverse proxy (nginx/Caddy), TLS, SMTP, backups, S3 storage, rate limiting, or hardening for production. Provides ready-to-use configs.
.claude/skills/davila7-pocketbase-deploy/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 132% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 107% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 74% | 0% |
PocketBase is a single binary. No runtime dependencies.
bash# Download wget https://github.com/pocketbase/pocketbase/releases/download/v0.X.X/pocketbase_0.X.X_linux_amd64.zip unzip pocketbase_*.zip chmod +x pocketbase # Run ./pocketbase serve --http="0.0.0.0:8090"
Data stored in pb_data/ (SQLite DB, uploaded files, logs).
ini# /etc/systemd/system/pocketbase.service [Unit] Description=PocketBase After=network.target [Service] Type=simple User=pocketbase Group=pocketbase LimitNOFILE=4096 Restart=always RestartSec=5s WorkingDirectory=/opt/pocketbase ExecStart=/opt/pocketbase/pocketbase serve --http="127.0.0.1:8090" # Security hardening NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/opt/pocketbase/pb_data /opt/pocketbase/pb_hooks /opt/pocketbase/pb_migrations PrivateTmp=true # Memory limit (adjust to your server) # MemoryMax=512M [Install] WantedBy=multi-user.target
bash# Setup sudo useradd --system --no-create-home pocketbase sudo mkdir -p /opt/pocketbase sudo cp pocketbase /opt/pocketbase/ sudo chown -R pocketbase:pocketbase /opt/pocketbase # Enable & start sudo systemctl daemon-reload sudo systemctl enable pocketbase sudo systemctl start pocketbase sudo systemctl status pocketbase # Logs sudo journalctl -u pocketbase -f
For high-traffic deployments, increase the limit:
ini# In the [Service] section: LimitNOFILE=65535
Also set system-wide in /etc/security/limits.conf:
pocketbase soft nofile 65535
pocketbase hard nofile 65535For constrained environments:
iniEnvironment=GOMEMLIMIT=400MiB
dockerfileFROM alpine:latest ARG PB_VERSION=0.25.0 RUN apk add --no-cache \ unzip \ ca-certificates # Download and install PocketBase # NOTE: verify the checksum in production — see https://github.com/pocketbase/pocketbase/releases ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip RUN unzip /tmp/pb.zip -d /pb/ && rm /tmp/pb.zip # Copy hooks and migrations COPY ./pb_hooks /pb/pb_hooks COPY ./pb_migrations /pb/pb_migrations EXPOSE 8090 CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090"]
yamlservices: pocketbase: build: . ports: - "127.0.0.1:8090:8090" # bind to localhost only — expose via reverse proxy volumes: - pb_data:/pb/pb_data - ./pb_hooks:/pb/pb_hooks - ./pb_migrations:/pb/pb_migrations restart: unless-stopped healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8090/api/health"] interval: 30s timeout: 5s retries: 3 volumes: pb_data:
# /etc/caddy/Caddyfile
myapp.com {
reverse_proxy localhost:8090
}That's it. Caddy handles TLS certificates automatically via Let's Encrypt.
nginx# /etc/nginx/sites-available/pocketbase server { listen 80; server_name myapp.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name myapp.com; ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; client_max_body_size 50M; # Block public access to the admin dashboard location /_/ { return 403; } location / { proxy_pass http://127.0.0.1:8090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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 for realtime proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; } }
Critical for realtime: proxy_buffering off and proxy_read_timeout must be set for SSE subscriptions to work.
bash# Let's Encrypt with nginx sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d myapp.com
Configure in Dashboard > Settings > Mail settings, or via hooks:
js// pb_hooks/settings.pb.js onBootstrap(function(e) { var settings = e.app.settings() settings.smtp.enabled = true settings.smtp.host = $os.getenv("SMTP_HOST") settings.smtp.port = parseInt($os.getenv("SMTP_PORT") || "587") settings.smtp.username = $os.getenv("SMTP_USER") settings.smtp.password = $os.getenv("SMTP_PASS") settings.smtp.tls = true // STARTTLS // settings.smtp.authMethod = "PLAIN" // or "LOGIN" settings.meta.senderName = "My App" settings.meta.senderAddress = "noreply@myapp.com" e.app.save(settings) return e.next() })
Always enable MFA for superuser accounts in production: Dashboard > Superusers > Auth options > MFA > Enable
Encrypt sensitive settings (SMTP passwords, S3 keys) at rest:
bash./pocketbase serve --encryptionEnv=PB_ENCRYPTION_KEY
Set PB_ENCRYPTION_KEY environment variable to a 32+ character random string. Once set, settings are encrypted in the DB. Do not lose this key — you won't be able to decrypt settings without it.
Built-in rate limiter (enabled by default). Configure in Dashboard > Settings > Rate limits, or:
jssettings.rateLimits.enabled = true settings.rateLimits.rules = [ { label: "*:auth*", maxRequests: 10, duration: 300 }, // 10 auth attempts per 5 min { label: "POST:/api/collections/*/records", maxRequests: 50, duration: 60 }, ]
bash./pocketbase serve --http="127.0.0.1:8090" # bind to localhost only
Access the dashboard only via SSH tunnel:
bashssh -L 8090:127.0.0.1:8090 user@server
For file uploads, offload to S3-compatible storage:
Dashboard > Settings > Files storage > S3
js// Or via hooks: onBootstrap(function(e) { var settings = e.app.settings() settings.s3.enabled = true settings.s3.bucket = $os.getenv("S3_BUCKET") settings.s3.region = $os.getenv("S3_REGION") settings.s3.endpoint = $os.getenv("S3_ENDPOINT") settings.s3.accessKey = $os.getenv("S3_ACCESS_KEY") settings.s3.secret = $os.getenv("S3_SECRET") settings.s3.forcePathStyle = true // for MinIO/Backblaze e.app.save(settings) return e.next() })
Compatible providers: AWS S3, Backblaze B2, Cloudflare R2, MinIO, DigitalOcean Spaces, Wasabi.
Use the built-in backup feature:
POST /api/backupsThe Dashboard backup uses SQLite's online backup API (locks DB briefly). For large DBs, use:
bash# sqlite3 .backup command (hot backup, minimal locking) sqlite3 /opt/pocketbase/pb_data/data.db ".backup '/tmp/backup.db'" # Then rsync to remote rsync -avz /tmp/backup.db backup-server:/backups/pocketbase/data-$(date +%Y%m%d).db
Never copy the .db file directly while PocketBase is running — it may be in an inconsistent state.
bash#!/bin/bash # /opt/pocketbase/backup.sh set -euo pipefail BACKUP_DIR="/backups/pocketbase" DB_PATH="/opt/pocketbase/pb_data/data.db" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p "$BACKUP_DIR" # Hot backup sqlite3 "$DB_PATH" ".backup '${BACKUP_DIR}/data_${DATE}.db'" # Also backup pb_data files (uploads, if not using S3) tar -czf "${BACKUP_DIR}/pb_data_${DATE}.tar.gz" -C /opt/pocketbase pb_data --exclude='pb_data/data.db*' # Retain last 30 days find "$BACKUP_DIR" -name "data_*.db" -mtime +30 -delete find "$BACKUP_DIR" -name "pb_data_*.tar.gz" -mtime +30 -delete
bash# Crontab: daily at 2 AM 0 2 * * * /opt/pocketbase/backup.sh >> /var/log/pocketbase-backup.log 2>&1
bashcurl http://localhost:8090/api/health # {"code":200,"message":"API is healthy."}
--encryptionEnv set for sensitive settings| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 15,743 | 11,745 | -25% | 1 | 1 | 0% | 3,206 | 5,398 | +68% | 0 | 0 | — |
case-02 | fail→pass | 13,425 | 13,996 | +4% | 1 | 1 | 0% | 2,766 | 6,019 | +118% | 0 | 0 | — |
case-03 | fail→fail | 18,125 | 14,609 | -19% | 1 | 1 | 0% | 3,306 | 6,185 | +87% | 0 | 0 | — |
case-04 | fail→fail | 9,962 | 7,302 | -27% | 1 | 1 | 0% | 1,751 | 4,271 | +144% | 0 | 0 | — |
case-05 | fail→pass | 8,942 | 3,389 | -62% | 1 | 1 | 0% | 1,543 | 3,580 | +132% | 0 | 0 | — |
case-06 | pass→pass | 11,208 | 6,932 | -38% | 1 | 1 | 0% | 2,253 | 4,379 | +94% | 0 | 0 | — |
case-07 | pass→pass | 10,821 | 4,888 | -55% | 1 | 1 | 0% | 1,947 | 3,894 | +100% | 0 | 0 | — |
case-08 | pass→pass | 9,565 | 7,709 | -19% | 1 | 1 | 0% | 1,965 | 4,532 | +131% | 0 | 0 | — |
case-09 | fail→pass | 12,358 | 11,112 | -10% | 1 | 1 | 0% | 2,617 | 5,405 | +107% | 0 | 0 | — |
case-10 | fail→pass | 12,670 | 9,010 | -29% | 1 | 1 | 0% | 2,858 | 4,959 | +74% | 0 | 0 | — |
case-11 | pass→pass | 14,459 | 9,599 | -34% | 1 | 1 | 0% | 2,915 | 5,057 | +73% | 0 | 0 | — |
case-12 | fail→pass | 13,553 | 10,640 | -21% | 1 | 1 | 0% | 3,206 | 5,293 | +65% | 0 | 0 | — |
case-13 | pass→pass | 5,603 | 2,836 | -49% | 1 | 1 | 0% | 1,090 | 3,362 | +208% | 0 | 0 | — |
case-14 | pass→pass | 5,745 | 3,455 | -40% | 1 | 1 | 0% | 1,247 | 3,596 | +188% | 0 | 0 | — |
case-15 | pass→pass | 6,376 | 4,554 | -29% | 1 | 1 | 0% | 1,437 | 3,871 | +169% | 0 | 0 | — |
case-16 | pass→pass | 12,600 | 8,740 | -31% | 1 | 1 | 0% | 2,382 | 4,589 | +93% | 0 | 0 | — |
case-17 | fail→pass | 14,448 | 10,835 | -25% | 1 | 1 | 0% | 3,010 | 5,243 | +74% | 0 | 0 | — |
case-18 | fail→fail | 5,451 | 5,651 | +4% | 1 | 1 | 0% | 968 | 3,980 | +311% | 0 | 0 | — |
case-19 | pass→fail | 2,316 | 1,967 | -15% | 1 | 1 | 0% | 464 | 3,231 | +596% | 0 | 0 | — |
case-20 | pass→pass | 14,980 | 12,100 | -19% | 1 | 1 | 0% | 3,203 | 5,777 | +80% | 0 | 0 | — |
case-21 | pass→pass | 21,144 | 23,276 | +10% | 1 | 1 | 0% | 4,708 | 8,361 | +78% | 0 | 0 | — |
case-22 | pass→pass | 8,699 | 8,596 | -1% | 1 | 1 | 0% | 2,095 | 5,005 | +139% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.