crane is a lightweight, zero-Docker deployment tool written in Rust that provisions and deploys application services and high-availability database clusters directly to Ubuntu/Debian VPS nodes over SSH.
- Zero-Docker App Deploys: Runs your binary directly on remote nodes as systemd template services.
- High-Availability PostgreSQL: Automated cluster topology with Patroni, etcd as DCS, and HAProxy for smart write/read routing.
- Built-in Reverse Proxy & SSL: Automatically configures load-balancing and SSL certificates.
- Automated S3 Backups: Integrated scheduling for database backups (full/incremental) to S3/MinIO.
- Firewall Setup: Configures firewall for internal cluster ports while exposing public HTTP (80), HTTPS (443), and SSH (22).
- Cloudflare DNS Sync: Syncs public node IPs to Cloudflare A records automatically.
-
Install Crane: Compile or download the
cranebinary and place it in your$PATH. -
Scaffold Configuration: Create a
crane.tomlconfiguration and.envsecrets file in your repository:# crane.toml [[nodes]] name = "vps1" public_ip = "10.0.0.11" internal_ip = "10.0.0.11" roles = ["app", "haproxy", "postgres", "redis"] user = "crane" ssh_ip = "127.0.0.1" ssh_port = 2221 private_key = "keys/id_ed25519" sudo_pass = "${SUDO_PASS_VPS1}" [[nodes]] name = "vps2" public_ip = "10.0.0.12" internal_ip = "10.0.0.12" roles = ["app", "haproxy", "postgres", "redis"] user = "crane" ssh_ip = "127.0.0.1" ssh_port = 2222 private_key = "keys/id_ed25519" sudo_pass = "${SUDO_PASS_VPS2}" [[nodes]] name = "vps3" public_ip = "10.0.0.13" internal_ip = "10.0.0.13" roles = ["app", "haproxy", "postgres", "redis"] user = "crane" ssh_ip = "127.0.0.1" ssh_port = 2223 private_key = "keys/id_ed25519" sudo_pass = "${SUDO_PASS_VPS3}" [[users]] name = "deployman" groups = ["www"] ssh_authorized_keys = ["keys/id_ed25519.pub"] private_key = "keys/id_ed25519" [app.myapp] name = "myapp" # Setup Info dependencies = ["libssl3", "ca-certificates"] # Deploy Info deploy_dir = "./demo" entrypoint = "./myapp" pre_deploy_script = "./before-deploy.sh" deploy_user = "deployman" port_start = 3000 port_end = 3100 # Health Check health_check_path = "/health" health_check_timeout = 30 health_check_interval = 2 # Domain domain = "myapp.localhost" # Replicas instances = 1 # ENV for myapp [app.myapp.env] LOG_LEVEL = "debug" APP_NAME = "myapp" # Databases for myapp [[app.myapp2.database]] databases = "mydb" user = "u1" [app.myapp2] name = "myapp2" # Setup Info dependencies = ["libssl3", "ca-certificates"] # Deploy Info deploy_dir = "./demo" entrypoint = "./myapp" deploy_user = "deployman" port_start = 4000 port_end = 4100 # Health Check health_check_path = "/health" health_check_timeout = 30 health_check_interval = 2 # Domain domain = "myapp2.localhost" # Replicas instances = 2 # ENV for myapp2 [app.myapp2.env] LOG_LEVEL = "debug" APP_NAME = "myapp2" # Databases for my app2 [[app.myapp2.database]] databases = "mydb" user = "u1" [db.postgres] enabled = true version = "17" replica_pass = "replica" [db.postgres.backup] full_backup_every = "1h" incremental_backup_every = "15m" [db.postgres.mydb] name = "mydb" [[db.postgres.users]] # state = "present" / "absent" state = "present" user = "u1" password = "u1" databases = ["mydb"] [[db.postgres.users]] state = "present" user = "u2" password = "u2" databases = ["mydb"] [backup.s3] bucket = "crane1" region = "us-east-1" endpoint = "http://s3:9000" access_key_id = "${S3_ACCESS_KEY_ID}" secret_access_key = "${S3_SECRET_ACCESS_KEY}" [domain] provider = "cloudflare" domain_name = "localhost" token = "${CLOUDFLARE_TOKEN}"
-
Deploy: Make sure environment variables referenced in
crane.toml(such asSUDO_PASS_VPS1,SUDO_PASS_VPS2,SUDO_PASS_VPS3,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY, andCLOUDFLARE_TOKEN) are set in your current shell or defined in your.envfile before deploying:crane deploy
- Multiplexed SSH: Crane opens a single multiplexed master SSH connection (
ControlMaster) to each node. - Firewall Provisioning: configures the nodes firewall to only expose ports 22, 80, and 443 publicly, and fully whitelist inter-node communication.
- Database Topology: Configures Patroni and etcd clusters on nodes with the
postgresrole. - App Delivery: Compresses the application, transfers it over SCP, extracts it, merges environmental variables, starts the systemd service instances, and polls health checks.
- Reverse Proxying: Hooks up HAProxy/Traefik reverse proxies to distribute incoming public traffic across the running instances.
| Command | Description |
|---|---|
crane deploy |
Deploy the application and database cluster. |
crane status |
Display cluster status, node health, and app instances. |
crane logs <app> |
Stream application standard output/error logs. |
| Command | Description |
|---|---|
crane pg status |
Inspect the PostgreSQL active primary/replica topologies and replication lag. |
crane pg promote --node <host> |
Force-promote a specific node to primary database leader. |
crane pg backup <full|incr> |
Trigger a manual full or incremental cluster database backup to S3. |
crane pg list |
List available database backups. |
crane pg restore <backup_id> [--pitr <TIME>] |
Restore the database state from an S3 backup ID. with Point-in-time recovery support. --pitr <YYYY-MM-DD HH:MM:SS> |
- Rust (latest stable edition)
- Docker and Docker Compose (to run the simulated VPS setup)
-
Start the Simulated VPS Cluster:
# Install required packages for dev ./devsetup.sh # Setup dev SSH keys ./setup-ssh.sh # Setup docker for simulate VPS servers setup ./setup-docker.sh
-
Run Code Verification: Ensure you use
cargo nextestto execute tests. Run single test suites at a time to prevent performance bottlenecks:cargo nextest run --test deploy -- test_single_deploy --nocapture
Contributions are highly welcome! Feel free to open issues or submit pull requests. For large architectural changes, please open an issue first to discuss the design.