A production-grade, local-only deployment automation tool written in Go. Designed for paranoid systems engineers who manage 50+ production applications across multiple VPS servers.
- Local-Only: All configuration, secrets, and deployment logic remain on your local machine
- Zero Server State: Servers are execution-only targets with no deployment metadata stored
- Automatic Rollback: Failed migrations trigger immediate rollback of database and files
- Full Audit Trail: Every deployment step is logged locally with timestamps
- Interactive Setup: Scaffold new apps easily using an interactive wizard
- Automated Provisioning: Automatically configure Nginx, SSL (Certbot), and PM2
- Local Build Pipeline: Build locally and only upload the
dist/builddirectory to save bandwidth - Environment Management: Inject build-time variables and securely sync server variables
- SSH key-based authentication (no passwords)
- YAML configuration files for servers and applications
- Automatic database backups before deployments (MySQL/PostgreSQL)
- Automatic application file backups
- Rsync-based file synchronization
- Build, migrate, and restart command execution
- HTTP and process-based health checks
- Automatic rollback on migration failure
- Manual rollback command
- Backup retention management
- Multi-runtime support (Node.js, PHP, Python, Go, Ruby)
- Go 1.21 or later
- rsync installed locally
- SSH access to target servers
# Clone or download to your machine
cd server-manager
# Download dependencies
go mod download
# Build the binary
go build -o bin/deployer ./cmd/deployer
# Or use the build script
go run build.go
# Build for all platforms
go run build.go --allsudo cp bin/deployer /usr/local/bin/server-manager/
├── bin/ # Compiled binaries
├── cmd/
│ └── deployer/
│ └── main.go # CLI entry point
├── internal/
│ ├── config/ # Configuration parsing
│ ├── ssh/ # SSH client wrapper
│ ├── backup/ # Backup/restore operations
│ ├── deploy/ # Deployment orchestration
│ ├── rollback/ # Rollback operations
│ ├── health/ # Health checks
│ ├── status/ # Status reporting
│ └── logger/ # Logging system
├── configs/
│ ├── servers/ # Server configurations (*.yaml)
│ └── apps/ # Application configurations (*.yaml)
├── backups/ # Local backup metadata
├── releases/ # Release artifacts
├── logs/ # Deployment logs
├── go.mod
└── README.md
Create a YAML file in configs/servers/:
# configs/servers/production-web-01.yaml
name: production-web-01
host: 192.168.1.100
ssh_user: deploy
ssh_port: 22
# Authentication - choose ONE of the following options:
# Option 1: SSH Key (RECOMMENDED)
auth_type: ssh_key
ssh_key_path: ~/.ssh/deploy_key
# Option 2: Plain Password
# auth_type: password
# ssh_password: your_password_here
# Option 3: Base64 Encoded Password
# auth_type: base64_password
# ssh_password_b64: eW91cl9wYXNzd29yZA==
# Global Backup Options (Used by `deployer server-backup`)
database:
mysql:
user: root
password: my_password
port: 3306
localdatabases_backup_path: ~/backups/servers
ecosystem_file_path: /var/www/ecosystem.config.js| Type | Field | Description |
|---|---|---|
ssh_key |
ssh_key_path |
Path to SSH private key (recommended) |
password |
ssh_password |
Plain text password |
base64_password |
ssh_password_b64 |
Base64 encoded password |
Generating Base64 Password:
echo -n "your_password" | base64
# Output: eW91cl9wYXNzd29yZA==Note: For password-based authentication, you need sshpass installed:
# Debian/Ubuntu
sudo apt install sshpass
# RHEL/CentOS
sudo yum install sshpass
# macOS
brew install hudochenkov/sshpass/sshpassGenerate configurations using the interactive wizard (deployer init my-app), or create a YAML file in configs/apps/:
# configs/apps/my-app.yaml
app_name: my-app
server_name: production-web-01
local_path: ~/projects/my-app
app_path: /var/www/my-app
runtime: node
port: 3000
# Used by `deployer provision`
domains:
- my-app.example.com
# Build Configuration
local_build_cmd: npm ci && npm run build
build_dir: dist
build_command: npm ci --production
# Environment Variables
env_build:
NODE_ENV: production
env_server:
DATABASE_URL: postgres://user:pass@localhost:5432/mydb
PORT: "3000"
migrate_command: npm run migrate
restart_command: pm2 restart my-app || pm2 start ecosystem.config.js
# Specific directories to backup out of the app tree when using `deployer backup-dirs`
directories_for_backup:
- "public/uploads"
- "storage/app"
# Directories to preserve on server and NEVER overwrite or delete (e.g., user uploads, static files)
persistent_paths:
- "public/uploads"
- "storage/app"
# Whether to delete files on the server that are not in the local directory (default: false)
delete_on_deploy: false
health_check:
type: http
endpoint: /health
port: 3000
timeout: 30
retries: 3
database:
type: postgres
name: my_app_db
user: app_user
password: "your_secure_password"
host: localhost
port: 5432
backup_path: /var/backups/my-app
exclude_files:
- "*.log"
- "tmp/*"deployer init my-appdeployer provision my-app# Basic deployment
deployer deploy my-app
# Deploy with specific steps skipped
deployer deploy --skip-migration my-app
deployer deploy --skip-server-build --skip-restart my-app
# Force upload (skip database and app backups)
deployer deploy --force my-appDeployment Options:
--skip-migration: Skip the database migration step--skip-server-build: Skip the server build step (build_command)--skip-restart: Skip the application restart step--skip-backup: Skip the database and application backup steps--skip-health-check: Skip the health check step--force: Force upload (equivalent to skipping backups)
deployer rollback my-app# Backup app specifically (app files + app database)
deployer backup my-app
# Backup specific directories defined in directories_for_backup
deployer backup-dirs my-app
# Restore specific directories zip over live path
deployer restore-dirs my-app /path/to/dirs.zip
# Backup entire server globally (all mysql + postgres dbs + pm2 ecosystem)
deployer server-backup my-server
# Restore entire server from zip (seeds all db files over pipes)
deployer server-restore my-server /path/to/server.zip# Single app
deployer status my-app
# All apps
deployer status --alldeployer list# Validate specific app
deployer validate my-app
# Validate all configs
deployer validatedeployer logs my-appdeployer backups my-appdeployer -dry-run deploy my-appdeployer -v deploy my-appThe deployment process follows these exact steps:
- Validate Configuration - Verify all configs are valid
- Local Build - Optional: Run
local_build_cmdand injectenv_buildvariables - Connect to Server - Establish SSH connection
- Backup Database - Optional: Create timestamped database dump
- Backup Application - Copy current app directory
- Upload Files - Rsync new files (or just
build_dir) to server - Sync Environment Variables - Optional: Generate and upload
.envsecurely - Run Build Command - Optional: Execute build on remote server
- Run Migrations - Optional: Execute database migrations
- Restart Application - Restart the service via PM2/systemd
- Health Check - Verify application is running
If any step after backup fails, the system automatically:
- Restores the database from backup
- Restores the application files from backup
- Restarts the application
- Logs the failure and rollback
- No secrets on servers: Database passwords never leave your machine
- SSH key authentication only: No password-based SSH
- Timestamped backups: Full audit trail with checksums
- Local-only configs: Nothing sensitive stored remotely
- Minimal dependencies: Only 2 external Go packages (both from trusted sources)
- Create a dedicated deploy user:
sudo useradd -m -s /bin/bash deploy
sudo mkdir -p /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh- Generate SSH key (on your local machine):
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -C "deploy@server-manager"
chmod 600 ~/.ssh/deploy_key- Copy public key to server:
ssh-copy-id -i ~/.ssh/deploy_key.pub deploy@your-server- Grant necessary permissions:
# Allow deploy user to restart services (add to sudoers)
echo "deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart *" | sudo tee /etc/sudoers.d/deploy
echo "deploy ALL=(ALL) NOPASSWD: /bin/systemctl reload php*-fpm" | sudo tee -a /etc/sudoers.d/deploy- Set correct file permissions:
# Config files should only be readable by you
chmod 600 configs/servers/*.yaml
chmod 600 configs/apps/*.yaml- SSH keys have 600 permissions
- Config files have 600 permissions
- Deploy user has minimal sudo privileges
- Servers have fail2ban or similar
- SSH root login is disabled
- SSH password authentication is disabled
- Database passwords are strong (32+ characters)
- Backup directories have restricted permissions
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (deployment failed, no rollback) |
| 2 | Deployment failed but rollback succeeded |
# Test SSH connection manually
ssh -i ~/.ssh/deploy_key -p 22 deploy@your-server
# Check key permissions
ls -la ~/.ssh/deploy_key # Should be -rw-------# Verify rsync is installed locally
which rsync
# Test rsync manually
rsync -avz -e "ssh -p 22 -i ~/.ssh/deploy_key" ./test/ deploy@server:/tmp/test/# Verify database credentials by SSH-ing to server
ssh deploy@server
mysqldump --user=dbuser --password=dbpass dbname > /dev/null
# or
PGPASSWORD='dbpass' pg_dump -U dbuser dbname > /dev/nullThis is a single-purpose tool designed for reliability over features. Keep it simple.
MIT License - Use at your own risk. Always test in staging first.