Main instructions for AI ->
AGENTS.md. This document is a detailed reference -- consult it when you need specific commands, diagnostic procedures, or architecture details.
Complete technical documentation for StackPilot. Intended for AI agents and humans alike.
- Server Connection
- Available Applications
- Deployment Commands
- Backup and Restore
- Diagnostics and Troubleshooting
- Missing Tools
- Architecture
- Limits and Constraints
Servers are accessed via SSH. The alias is configured in ~/.ssh/config:
# Check available aliases
grep "^Host " ~/.ssh/config
# Typical aliases: vps, production, staging, etc.# Test connectivity
ssh vps 'echo "OK: $(hostname)"'
# Check server information
ssh vps 'hostname && cat /etc/os-release | head -2'On a fresh VPS, ensure Docker is installed:
ssh vps
# Install Docker:
curl -fsSL https://get.docker.com | shVerify Docker installation:
ssh vps 'docker --version'
# If it fails: install Docker as shown aboveWindows users after SSH setup (setup-ssh.ps1) can run scripts directly on the server:
# 1. From your local machine - install toolbox on the server:
./local/install-toolbox.sh vps
# 2. Connect to the server:
ssh vps
# 3. Run scripts directly:
deploy.sh uptime-kumaEnvironment detection: scripts automatically detect whether they are running on the server and skip SSH -- commands execute directly.
Local-only scripts (do not work on the server): setup-ssh.sh, sync.sh
Applications are located in apps/<name>/install.sh:
| Application | Description | Database | Port |
|---|---|---|---|
| uptime-kuma | Service monitoring (like UptimeRobot) | - | 3001 |
| ntfy | Push notifications | - | 8085 |
| filebrowser | Web file manager | - | 8095 |
| dockge | Docker Compose management UI | - | 5001 |
| stirling-pdf | Online PDF tools | - | 8087 |
| n8n | Workflow automation | PostgreSQL* | 5678 |
| umami | Web analytics (alt. Google Analytics) | PostgreSQL* | 3000 |
| nocodb | Database (alt. Airtable) | PostgreSQL | 8080 |
| listmonk | Newsletter and mailing | PostgreSQL* | 9000 |
| keila | Email marketing (alt. Mailchimp/Brevo) | PostgreSQL | 4500 |
| typebot | Chatbot builder | PostgreSQL* | 8081/8082 |
| vaultwarden | Password manager (Bitwarden) | SQLite | 8088 |
| linkstack | Link page (alt. Linktree) | SQLite | 8090 |
| redis | Cache / key-value store | - | 6379 |
| wordpress | CMS (Performance Edition: FPM+Nginx+Redis) | MySQL/SQLite | 8080 |
| convertx | File converter (100+ formats) | SQLite | 3000 |
| postiz | Social media scheduler | PostgreSQL* | 5000 |
| crawl4ai | Web crawler with AI extraction | - | 8000 |
| cap | Screen recording and sharing | MySQL | 3000 |
| sellf | Digital product sales / launch page | PostgreSQL (Supabase) | 3333 |
| minio | Object storage (S3-compatible) | - | 9000 |
| gotenberg | Document conversion API (PDF) | - | 3000 |
| cookie-hub | Consent management (GDPR) | - | 8091 |
| littlelink | Link page (simpler alternative) | - | 8090 |
| social-media-generator | Social media graphics from templates | PostgreSQL | 8000 |
| mcp-docker | MCP server for Docker management | - | - |
*PostgreSQL with an asterisk requires gen_random_uuid() (PG 13+). Applies to: n8n, umami, listmonk, typebot, postiz. Use bundled or a dedicated database (PG 13+).
WordPress is a special application with its own Dockerfile (PHP redis ext + WP-CLI), bundled Redis, auto-tuning FPM based on RAM, and a post-install script wp-init.sh. Details: apps/wordpress/README.md.
| Script | Description | Usage |
|---|---|---|
deploy.sh |
Install applications | ./local/deploy.sh APP [options] |
dns-add.sh |
Add Cloudflare DNS record | ./local/dns-add.sh DOMAIN [SSH] |
add-static-hosting.sh |
Static file hosting | ./local/add-static-hosting.sh DOMAIN [SSH] [LOCAL_DIR] [REMOTE_DIR] |
add-php-hosting.sh |
PHP site hosting | ./local/add-php-hosting.sh DOMAIN [SSH] [DIR] |
setup-backup.sh |
Configure backups | ./local/setup-backup.sh [SSH] |
restore.sh |
Restore from backup | ./local/restore.sh [SSH] |
setup-cloudflare.sh |
Configure Cloudflare API | ./local/setup-cloudflare.sh |
setup-turnstile.sh |
Configure Turnstile (CAPTCHA) | ./local/setup-turnstile.sh DOMAIN [SSH] |
sync.sh |
File sync (rsync) | ./local/sync.sh up/down SRC DEST [--ssh=ALIAS] |
./local/deploy.sh APP [options]
# Options:
# --ssh=ALIAS SSH alias (default: vps)
# --domain-type=TYPE cloudflare | caddy | local
# --domain=DOMAIN Domain for the app
# --db-source=SOURCE bundled | custom (databases)
# --yes, -y Skip all confirmation prompts
# --dry-run Show what would be done without executing
# Examples:
./local/deploy.sh n8n --ssh=vps --domain-type=cloudflare --domain=n8n.example.com
./local/deploy.sh uptime-kuma --ssh=vps --domain-type=local --yes
./local/deploy.sh sellf --ssh=vps --domain-type=cloudflare --domain=sellf.example.comdeploy.sh flow:
- Confirms deployment
- Asks about database (if required)
- Asks about domain (Cloudflare/Caddy/local)
- Performs resource checks (RAM, disk, ports)
- Executes installation
- Configures domain (after service is running)
- Shows summary
./local/sync.sh up <local_path> <remote_path> [--ssh=ALIAS]
./local/sync.sh down <remote_path> <local_path> [--ssh=ALIAS]
# Options:
# --ssh=ALIAS SSH alias (default: vps)
# --dry-run Show what would happen without executing
# Examples:
./local/sync.sh up ./my-website /var/www/html --ssh=vps
./local/sync.sh down /opt/stacks/n8n/.env ./backup/ --ssh=prod
./local/sync.sh up ./dist /var/www/public/app --dry-runA simple rsync wrapper for quick file transfers. Ideal for:
- Editing configuration locally (download -> edit -> upload)
- Uploading static sites to the server
- Backing up individual files
./local/dns-add.sh <subdomain.domain.com> [ssh_alias] [mode]
# Requires: ./local/setup-cloudflare.sh (one-time setup)
# Examples:
./local/dns-add.sh app.example.com vps # AAAA record (IPv6)
./local/dns-add.sh api.example.com vps ipv4 # A record (IPv4)./local/add-static-hosting.sh DOMAIN [SSH_ALIAS] [LOCAL_DIR] [REMOTE_DIR]
# Examples:
./local/add-static-hosting.sh static.example.com vps # files already on server at /var/www/static.example.com
./local/add-static-hosting.sh cdn.example.com vps ./dist # upload ./dist -> /var/www/cdn.example.com
./local/add-static-hosting.sh cdn.example.com vps ./dist /var/www/assets # upload ./dist -> /var/www/assets./local/add-php-hosting.sh DOMAIN [SSH_ALIAS] [DIRECTORY]
# Examples:
./local/add-php-hosting.sh app.example.com
./local/add-php-hosting.sh app.example.com vps /var/www/appDeploys Caddy + PHP-FPM on the host. Auto-installs both if missing.
./local/deploy.sh system/docker-setup.sh # Install Docker
./local/deploy.sh system/caddy-install.sh # Install Caddy (reverse proxy)
./local/deploy.sh system/power-tools.sh # CLI tools (yt-dlp, ffmpeg, pup)
./local/deploy.sh system/bun-setup.sh # Install Bun + PM2All applications store data in /opt/stacks/<app>/ using bind mounts.
The backup-core.sh script uses rclone to sync this directory to the cloud.
/opt/stacks/ Cloud (Google Drive, Dropbox, etc.)
|-- uptime-kuma/data/ ---> vps-backup/stacks/uptime-kuma/data/
|-- ntfy/cache/ ---> vps-backup/stacks/ntfy/cache/
|-- vaultwarden/data/ ---> vps-backup/stacks/vaultwarden/data/
+-- ...
# Run the wizard - configures rclone and cron
./local/setup-backup.sh vps
# Wizard steps:
# 1. Choose provider (Google Drive, Dropbox, OneDrive, S3...)
# 2. Log in via browser (OAuth)
# 3. Optionally enable encryption
# 4. Done - cron will run backup daily at 3:00 AMFor apps using PostgreSQL or MySQL, an automatic daily database dump can be configured:
# Via MCP:
setup_backup(backup_type='db')
# Or manually on the server:
# The setup-db-backup.sh script auto-detects running database containers
# and sets up a cron job for daily dumps.# Run backup now
ssh vps '~/backup-core.sh'
# Check logs
ssh vps 'tail -30 /var/log/stackpilot-backup.log'
# See what is in the cloud
ssh vps 'rclone ls backup_remote:vps-backup/stacks/'# Restore all data from the cloud
./local/restore.sh vps
# Or manually - restore a specific app
ssh vps 'rclone sync backup_remote:vps-backup/stacks/uptime-kuma /opt/stacks/uptime-kuma'
ssh vps 'cd /opt/stacks/uptime-kuma && docker compose up -d'# Check if cron is set
ssh vps 'crontab -l | grep backup'
# Check last backup
ssh vps 'tail -10 /var/log/stackpilot-backup.log'
# Compare local vs cloud
ssh vps 'rclone check /opt/stacks backup_remote:vps-backup/stacks/'# List running containers
ssh vps 'docker ps'
# Details with ports
ssh vps 'docker ps --format "table {{.Names}}\t{{.Ports}}\t{{.Status}}"'
# All containers (including stopped)
ssh vps 'docker ps -a'# Logs for a specific app
ssh vps 'cd /opt/stacks/uptime-kuma && docker compose logs --tail 50'
# Follow logs in real time
ssh vps 'cd /opt/stacks/uptime-kuma && docker compose logs -f'
# Logs with timestamps
ssh vps 'cd /opt/stacks/uptime-kuma && docker compose logs --tail 50 -t'# Check if the app responds on its port
ssh vps 'curl -s localhost:3001 | head -5'
# Check HTTP headers
ssh vps 'curl -sI localhost:3001'
# Test from outside (via domain)
curl -sI https://status.example.com# Check logs
ssh vps 'cd /opt/stacks/<app> && docker compose logs --tail 100'
# Check if the image was pulled
ssh vps 'docker images | grep <app>'
# Restart the container
ssh vps 'cd /opt/stacks/<app> && docker compose restart'# Check if the database is reachable
ssh vps 'nc -zv <db_host> 5432'
# Test PostgreSQL connection
ssh vps 'PGPASSWORD=<pass> psql -h <host> -U <user> -d <db> -c "SELECT 1"'- Check if the container is running:
docker ps - Check if the port is open:
curl localhost:PORT - Check if the port is NOT bound to 127.0.0.1 when using external domain access (must be 0.0.0.0 or no prefix)
- Verify DNS records point to the correct server IP
- Check Caddy status:
ssh vps 'systemctl status caddy'
# Check disk usage
ssh vps 'df -h /'
# Clean unused Docker images
ssh vps 'docker system prune -af'
# Truncate container logs
ssh vps 'truncate -s 0 /var/lib/docker/containers/*/*-json.log'Ensure the port binding allows external access:
# WRONG - localhost only
ports:
- "127.0.0.1:3000:3000"
# CORRECT - all interfaces (needed for external reverse proxy)
ports:
- "3000:3000"For Caddy reverse proxy, 127.0.0.1 binding is fine since Caddy runs on the same server.
# Restart
ssh vps 'cd /opt/stacks/<app> && docker compose restart'
# Full restart (down + up)
ssh vps 'cd /opt/stacks/<app> && docker compose down && docker compose up -d'
# Restart with new image
ssh vps 'cd /opt/stacks/<app> && docker compose pull && docker compose up -d'# Stop and remove containers (keep data)
ssh vps 'cd /opt/stacks/<app> && docker compose down'
# Stop, remove containers and data (volumes)
ssh vps 'cd /opt/stacks/<app> && docker compose down -v'
# Remove completely (containers + files)
ssh vps 'cd /opt/stacks/<app> && docker compose down -v && rm -rf /opt/stacks/<app>'Commonly pre-installed:
docker,docker compose(after docker-setup.sh)curl,wgetgitnano,vimhtop,ncdu
# Update packages (Debian/Ubuntu)
ssh vps 'apt update && apt install -y <package>'
# Examples:
ssh vps 'apt install -y jq' # JSON processor
ssh vps 'apt install -y tree' # Directory tree viewer
ssh vps 'apt install -y ncdu' # Disk usage analyzerThe system/power-tools.sh script installs:
yt-dlp- video downloadingffmpeg- media conversionpup- HTML parsing
./local/deploy.sh system/power-tools.sh/opt/stacks/ # Docker Compose applications
|-- uptime-kuma/
| |-- docker-compose.yaml
| +-- data/ # Application data (volumes)
|-- n8n/
+-- ...
- Requires Cloudflare account and API token
- DNS record (AAAA/A) pointing through Cloudflare proxy
- Caddy as reverse proxy with auto-SSL on the server
- Benefits: CDN, DDoS protection, IPv4-to-IPv6 translation
./local/setup-cloudflare.sh # One-time: configure API token
./local/dns-add.sh app.example.com vps # Add DNS record- Point DNS directly to server IP
- Caddy handles Let's Encrypt certificates automatically
- Simpler setup, no third-party dependency
- PostgreSQL or MySQL runs as a container alongside the app
- Credentials auto-generated by deploy.sh
- Data stored in
/opt/stacks/<app>/(backed up with the app) - Best for simplicity
- Provide your own database host, credentials, etc.
- Useful for managed database services or shared databases
- Full control over the database instance
- RAM: varies by VPS plan (512MB - 8GB+)
- Disk: varies by VPS plan (10GB - 80GB+)
- Always set memory limits in docker-compose.yaml!
deploy:
resources:
limits:
memory: 256M| Application | RAM Limit |
|---|---|
| uptime-kuma | 256M |
| ntfy | 128M |
| n8n | 512-768M |
| nocodb | 512M |
| vaultwarden | 128M |
- Ports 80 and 443 are used by Caddy (reverse proxy)
- Use ports > 1024 for applications
- Avoid conflicts - check
docker psbefore installing
# 1. Check Docker is installed
ssh vps 'docker --version' || ssh vps 'curl -fsSL https://get.docker.com | sh'
# 2. Install the application
./local/deploy.sh uptime-kuma --ssh=vps
# 3. Verify
ssh vps 'docker ps | grep uptime'
curl -sI https://status.example.com# 1. Check status
ssh vps 'docker ps -a | grep <app>'
# 2. Check logs
ssh vps 'cd /opt/stacks/<app> && docker compose logs --tail 50'
# 3. Test locally
ssh vps 'curl -s localhost:<port> | head -10'
# 4. Restart if needed
ssh vps 'cd /opt/stacks/<app> && docker compose restart'# Pull new image and restart
ssh vps 'cd /opt/stacks/<app> && docker compose pull && docker compose up -d'
# Clean old images
ssh vps 'docker image prune -f'