Overview
Capistrano provides a Ruby DSL for defining deployment workflows that execute commands on remote servers. The framework operates through SSH connections, executing deployment tasks in a structured, repeatable manner across multiple servers simultaneously.
The core architecture centers around the Capistrano::Application
class, which orchestrates task execution through Capistrano::Task
objects. Tasks define discrete operations like file transfers, service restarts, or database migrations. The Capistrano::Configuration
class manages server definitions, variables, and deployment parameters.
# config/deploy.rb
set :application, 'myapp'
set :repo_url, 'git@github.com:user/myapp.git'
set :deploy_to, '/var/www/myapp'
server 'web1.example.com', user: 'deploy', roles: %w{app web}
server 'web2.example.com', user: 'deploy', roles: %w{app web}
server 'db.example.com', user: 'deploy', roles: %w{db}
Server roles organize deployment targets by function. The app
role typically handles application code, web
manages web servers, and db
controls database operations. Capistrano executes tasks against servers based on their assigned roles.
task :restart_services do
on roles(:app) do
execute 'sudo systemctl restart myapp'
end
on roles(:web) do
execute 'sudo systemctl reload nginx'
end
end
The deployment process follows a structured workflow: Capistrano creates timestamped release directories on target servers, deploys code to these directories, then atomically switches a current
symlink to point to the new release. This approach enables instant rollbacks by pointing the symlink to a previous release directory.
Capistrano integrates with version control systems through SCM adapters. The Git adapter, most commonly used, clones repositories, checks out specific revisions, and transfers code to servers. Task dependencies ensure proper execution order, while hooks provide extension points for custom deployment logic.
Basic Usage
Capistrano deployment begins with generating configuration files through cap install
. This command creates the deployment structure with environment-specific configurations and task definitions.
# Generate Capistrano configuration
# Command: cap install
# Creates:
# - Capfile
# - config/deploy.rb
# - config/deploy/production.rb
# - config/deploy/staging.rb
# - lib/capistrano/tasks/
The Capfile
loads Capistrano components and custom tasks. Standard deployments require the base framework and SCM integration:
# Capfile
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/scm/git'
install_plugin Capistrano::SCM::Git
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
Basic deployment configuration defines application parameters, repository details, and server specifications. The :deploy_to
path becomes the deployment root containing releases, shared resources, and the current symlink:
# config/deploy.rb
lock '3.17.0'
set :application, 'blog_app'
set :repo_url, 'git@github.com:company/blog_app.git'
set :branch, 'main'
set :deploy_to, '/opt/apps/blog_app'
set :keep_releases, 5
append :linked_files, 'config/database.yml', 'config/secrets.yml'
append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/uploads'
Environment-specific configurations override global settings and define server inventories. Production environments typically specify multiple servers with distinct roles:
# config/deploy/production.rb
server 'app1.example.com', user: 'deploy', roles: %w{app web}, primary: true
server 'app2.example.com', user: 'deploy', roles: %w{app web}
server 'worker.example.com', user: 'deploy', roles: %w{app}
server 'db.example.com', user: 'deploy', roles: %w{db}
set :branch, 'production'
set :rails_env, 'production'
Task execution targets specific server roles through the on
method. Tasks can execute shell commands, transfer files, or manipulate remote directories:
namespace :app do
task :compile_assets do
on roles(:web) do
within release_path do
execute 'npm', 'install'
execute 'npm', 'run', 'build'
end
end
end
task :restart do
on roles(:app) do
execute 'sudo systemctl restart myapp'
end
end
end
Deployment execution follows the command pattern cap <environment> <task>
. The standard deployment task orchestrates the complete workflow:
# Deploy to production
# cap production deploy
# Deploy specific branch
# cap production deploy BRANCH=feature-123
# Execute custom task
# cap production app:restart
Advanced Usage
Advanced Capistrano deployments leverage custom variables, complex task dependencies, and sophisticated server targeting. Variable scoping enables environment-specific behavior while maintaining configuration consistency across stages.
# Dynamic variable assignment
set :timestamp, -> { Time.now.strftime('%Y%m%d%H%M%S') }
set :backup_path, -> { "#{shared_path}/backups/#{fetch(:timestamp)}" }
# Conditional variables
set :worker_processes, proc {
case fetch(:stage)
when :production then 4
when :staging then 2
else 1
end
}
# Server filtering by custom properties
server 'web1.example.com', user: 'deploy', roles: %w{web}, datacenter: 'us-east'
server 'web2.example.com', user: 'deploy', roles: %w{web}, datacenter: 'us-west'
task :deploy_by_datacenter do
on roles(:web).select { |s| s.properties.datacenter == 'us-east' } do
# Deploy only to east coast servers
end
end
Complex task orchestration requires explicit dependency management and hook integration. Tasks can specify prerequisites, define execution order, and integrate with the deployment lifecycle:
namespace :database do
task :backup => ['deploy:set_current_revision'] do
on roles(:db) do
backup_file = "backup_#{fetch(:current_revision)[0..7]}.sql"
execute 'pg_dump', 'myapp_production', '>', "#{fetch(:backup_path)}/#{backup_file}"
end
end
task :migrate => ['database:backup'] do
on primary(:db) do
within release_path do
with rails_env: fetch(:rails_env) do
execute 'bundle exec rails db:migrate'
end
end
end
end
end
# Hook into deployment lifecycle
before 'deploy:updated', 'database:backup'
after 'deploy:updated', 'database:migrate'
Server communication supports advanced SSH configuration including jump hosts, custom authentication, and connection pooling. Complex network topologies require explicit SSH configuration:
# SSH jump host configuration
set :ssh_options, {
user: 'deploy',
keys: %w(/home/deploy/.ssh/id_rsa),
forward_agent: true,
auth_methods: %w(publickey),
proxy: Net::SSH::Proxy::Command.new('ssh gateway.example.com -W %h:%p')
}
# Custom SSH configuration per server
server 'internal.example.com', user: 'deploy', roles: %w{app}, ssh_options: {
port: 2222,
keys: %w(/home/deploy/.ssh/internal_rsa)
}
Multi-stage deployments with shared resources require careful coordination. Blue-green deployments minimize downtime through parallel environment management:
namespace :bluegreen do
task :switch do
on roles(:web) do
current_color = test('[ -L /opt/apps/current ]') ?
capture('readlink /opt/apps/current').split('/').last : 'blue'
new_color = current_color == 'blue' ? 'green' : 'blue'
# Deploy to inactive environment
execute 'ln', '-sfn', "/opt/apps/#{new_color}", '/opt/apps/current'
# Update load balancer
execute 'curl', '-X POST', "http://lb.example.com/switch/#{new_color}"
end
end
task :prepare do
on roles(:app) do
['blue', 'green'].each do |color|
execute 'mkdir', '-p', "/opt/apps/#{color}/shared/log"
execute 'mkdir', '-p', "/opt/apps/#{color}/shared/tmp"
end
end
end
end
Error Handling & Debugging
Deployment failures require systematic diagnosis through log analysis, connection testing, and environment validation. Capistrano provides debugging mechanisms for identifying failure points in complex deployment workflows.
Connection failures manifest through SSH authentication errors, network timeouts, or permission problems. Verbose output reveals connection attempts and failure reasons:
# Enable SSH debugging
set :ssh_options, {
verbose: :debug,
logger: Logger.new($stdout)
}
# Test connectivity before deployment
task :check_connectivity do
on roles(:all) do |host|
begin
execute 'echo', 'Connection successful'
info "Connected to #{host.hostname}"
rescue SSHKit::Runner::ExecuteError => e
error "Connection failed to #{host.hostname}: #{e.message}"
raise
end
end
end
Command execution errors require analysis of exit codes, output streams, and environmental factors. Failed commands should provide diagnostic information:
task :safe_restart do
on roles(:app) do
begin
# Test service status before restart
service_status = capture 'systemctl is-active myapp || echo inactive'
if service_status.strip == 'inactive'
warn 'Service already stopped, starting instead of restarting'
execute 'sudo systemctl start myapp'
else
execute 'sudo systemctl restart myapp'
end
# Verify restart success
sleep 2
execute 'systemctl is-active myapp'
rescue SSHKit::Command::Failed => e
error "Service restart failed: #{e.message}"
# Gather diagnostic information
execute 'sudo journalctl -u myapp --no-pager -n 20'
execute 'sudo systemctl status myapp'
raise 'Deployment failed: unable to restart application service'
end
end
end
File transfer failures occur due to permission issues, disk space constraints, or network interruptions. Robust deployments validate preconditions and handle partial transfers:
namespace :deploy do
task :check_disk_space do
on roles(:app) do
available_mb = capture("df #{fetch(:deploy_to)} | tail -1 | awk '{print $4}'").to_i / 1024
required_mb = 500 # Minimum required MB
if available_mb < required_mb
error "Insufficient disk space: #{available_mb}MB available, #{required_mb}MB required"
raise 'Deployment aborted due to insufficient disk space'
end
info "Disk space check passed: #{available_mb}MB available"
end
end
task :verify_upload do
on roles(:app) do
# Verify critical files exist after upload
%w[Gemfile config.ru].each do |file|
unless test("[ -f #{release_path}/#{file} ]")
error "Critical file missing after upload: #{file}"
raise "Incomplete upload detected"
end
end
end
end
end
before 'deploy:updating', 'deploy:check_disk_space'
after 'deploy:updated', 'deploy:verify_upload'
Task execution debugging requires understanding execution context, variable scope, and timing issues. Debug output reveals execution flow:
task :debug_environment do
on roles(:app) do |host|
info "=== Debug Information for #{host.hostname} ==="
info "Deploy path: #{fetch(:deploy_to)}"
info "Current path: #{current_path}"
info "Release path: #{release_path}"
info "Shared path: #{shared_path}"
# Display environment variables
execute 'env | grep -E "(RAILS_ENV|NODE_ENV|PATH)" | sort'
# Check Ruby/Rails versions
within release_path do
execute 'bundle --version'
execute 'ruby --version'
end
# Verify symlinks
execute 'ls -la', current_path if test("[ -L #{current_path} ]")
end
end
Rollback scenarios require state validation and cleanup procedures. Failed deployments should restore previous stable states:
namespace :deploy do
task :rollback_with_verification do
on roles(:app) do
# Store current release for recovery
failed_release = release_path
# Execute standard rollback
invoke 'deploy:rollback'
# Verify rollback success
begin
within current_path do
execute 'bundle exec rails runner "puts Rails.env"'
end
info 'Rollback verification successful'
# Cleanup failed release
execute 'rm', '-rf', failed_release
rescue SSHKit::Command::Failed
error 'Rollback verification failed'
# Manual intervention required
raise 'Rollback completed but application failed to start'
end
end
end
end
Production Patterns
Production Capistrano deployments require zero-downtime strategies, health monitoring, and coordination with external systems. Deployment workflows integrate with load balancers, monitoring systems, and notification services.
Zero-downtime deployments coordinate application restarts with load balancer management. Servers exit rotation during updates, preventing request interruption:
namespace :loadbalancer do
task :remove_from_rotation do
on roles(:web) do |host|
# Remove server from load balancer
execute 'curl', '-X DELETE',
"http://lb-api.example.com/pool/production/#{host.hostname}"
# Wait for active connections to drain
sleep 30
# Verify no active connections
active_connections = capture('netstat -an | grep :80 | grep ESTABLISHED | wc -l').to_i
if active_connections > 5
warn "#{active_connections} connections still active on #{host.hostname}"
end
end
end
task :add_to_rotation do
on roles(:web) do |host|
# Add server back to load balancer
execute 'curl', '-X POST',
"http://lb-api.example.com/pool/production/#{host.hostname}"
# Verify health check passes
5.times do |attempt|
response = capture("curl -s -o /dev/null -w '%{http_code}' http://#{host.hostname}/health")
if response == '200'
info "Health check passed for #{host.hostname}"
break
elsif attempt == 4
error "Health check failed after 5 attempts"
raise "Server #{host.hostname} failing health checks"
else
sleep 10
end
end
end
end
end
before 'deploy:restart', 'loadbalancer:remove_from_rotation'
after 'deploy:restart', 'loadbalancer:add_to_rotation'
Database migration coordination prevents schema conflicts in multi-server environments. Migration tasks execute on single servers with proper locking:
namespace :database do
task :migrate_with_lock do
on primary(:db) do
within release_path do
with rails_env: fetch(:rails_env) do
# Acquire migration lock
lock_file = "#{shared_path}/db_migrate.lock"
if test("[ -f #{lock_file} ]")
lock_age = capture("stat -c %Y #{lock_file}").to_i
current_time = capture('date +%s').to_i
# Remove stale locks (older than 30 minutes)
if current_time - lock_age > 1800
execute 'rm', lock_file
warn 'Removed stale migration lock'
else
raise 'Migration already in progress'
end
end
begin
# Create lock file
execute 'touch', lock_file
# Run migrations
execute 'bundle exec rails db:migrate'
# Verify migration status
migration_status = capture('bundle exec rails db:migrate:status | grep " down " | wc -l').to_i
if migration_status > 0
error "#{migration_status} migrations failed to apply"
raise 'Migration verification failed'
end
ensure
# Always remove lock
execute 'rm', '-f', lock_file
end
end
end
end
end
end
Asset compilation and CDN integration require coordination between deployment and content delivery. Compiled assets upload to CDN endpoints before application restart:
namespace :assets do
task :compile_and_upload do
on roles(:web), in: :sequence do
within release_path do
with rails_env: fetch(:rails_env) do
# Compile assets with production optimizations
execute 'bundle exec rails assets:precompile'
# Generate asset manifest
asset_manifest = "#{release_path}/public/assets/.sprockets-manifest-*.json"
manifest_content = capture("cat #{asset_manifest}")
# Upload to CDN
execute 'aws s3 sync',
"#{release_path}/public/assets/",
's3://cdn.example.com/assets/',
'--delete --cache-control max-age=31536000'
# Update CDN cache headers
execute 'aws cloudfront create-invalidation',
'--distribution-id E123456789',
'--paths "/assets/*"'
end
end
end
end
task :verify_cdn_sync do
on roles(:web), limit: 1 do
# Test asset availability from CDN
test_asset = capture("ls #{release_path}/public/assets/application-*.js | head -1")
asset_name = File.basename(test_asset)
response = capture("curl -s -o /dev/null -w '%{http_code}' https://cdn.example.com/assets/#{asset_name}")
unless response == '200'
error 'CDN asset sync verification failed'
raise 'Assets not available from CDN'
end
info 'CDN asset sync verified'
end
end
end
Monitoring integration provides deployment visibility and failure notification. Deployment events integrate with APM and alerting systems:
namespace :monitoring do
task :notify_deployment_start do
run_locally do
# Notify monitoring system
execute 'curl', '-X POST',
'https://api.datadog.com/api/v1/events',
'-H "Content-Type: application/json"',
'-H "DD-API-KEY: $DATADOG_API_KEY"',
'-d', %Q{
{
"title": "Deployment Started",
"text": "#{fetch(:application)} deployment to #{fetch(:stage)} started",
"tags": ["deployment", "#{fetch(:stage)}", "#{fetch(:application)}"]
}
}
end
end
task :health_check_monitoring do
on roles(:web) do |host|
# Register deployment event
execute 'curl', '-X POST',
"http://monitoring.example.com/events",
'-d', "service=#{fetch(:application)}&host=#{host.hostname}&event=deployment_complete"
# Enable enhanced monitoring during deployment window
execute 'curl', '-X POST',
"http://monitoring.example.com/alerts/#{fetch(:application)}/enhanced",
'-d', 'duration=300' # 5 minutes enhanced monitoring
end
end
end
before 'deploy:started', 'monitoring:notify_deployment_start'
after 'deploy:finished', 'monitoring:health_check_monitoring'
Common Pitfalls
Capistrano deployments fail through subtle configuration issues, timing problems, and environmental inconsistencies. Understanding common failure patterns prevents deployment disruptions and reduces troubleshooting time.
SSH key authentication problems cause deployment failures when key forwarding fails or authentication agents lack required keys. Agent forwarding requires proper SSH configuration:
# Incorrect - agent forwarding disabled
set :ssh_options, {
user: 'deploy',
keys: %w(/home/deploy/.ssh/id_rsa)
}
# Correct - enable agent forwarding for Git operations
set :ssh_options, {
user: 'deploy',
keys: %w(/home/deploy/.ssh/id_rsa),
forward_agent: true,
auth_methods: %w(publickey)
}
# Test SSH agent forwarding
task :test_ssh_agent do
on roles(:app) do
# This should succeed with agent forwarding
execute 'ssh-add -l'
# Test Git access through forwarded agent
execute 'git ls-remote', fetch(:repo_url), 'HEAD'
end
end
File permission errors occur when deploy users lack ownership or write access to deployment directories. Permission issues manifest during file transfers or symlink creation:
# Setup proper permissions during server provisioning
namespace :provision do
task :setup_deploy_user do
on roles(:all) do
# Create deployment directory with proper ownership
execute 'sudo mkdir -p', fetch(:deploy_to)
execute 'sudo chown', "#{fetch(:user)}:#{fetch(:user)}", fetch(:deploy_to)
execute 'chmod', '755', fetch(:deploy_to)
# Setup shared directory structure
shared_dirs = fetch(:linked_dirs) + ['log', 'tmp', 'config']
shared_dirs.each do |dir|
execute 'mkdir', '-p', "#{shared_path}/#{dir}"
end
end
end
# Verify permissions before deployment
task :check_permissions do
on roles(:app) do
# Test write access to deployment directory
test_file = "#{fetch(:deploy_to)}/.deploy_test"
execute 'touch', test_file
execute 'rm', test_file
# Verify shared directory permissions
fetch(:linked_dirs).each do |dir|
unless test("[ -w #{shared_path}/#{dir} ]")
error "No write permission for #{shared_path}/#{dir}"
raise 'Permission check failed'
end
end
end
end
end
Symlink timing issues create race conditions when applications start before symlinks complete. Atomic symlink operations prevent partial deployment states:
# Problematic - non-atomic symlink creation
task :unsafe_symlink do
on roles(:app) do
execute 'rm', current_path
execute 'ln -s', release_path, current_path
# Race condition: application may start between rm and ln
end
end
# Safe - atomic symlink replacement
task :atomic_symlink do
on roles(:app) do
temp_link = "#{current_path}.tmp.#{Time.now.to_i}"
execute 'ln -s', release_path, temp_link
execute 'mv', temp_link, current_path
# Atomic operation: symlink replacement happens instantly
end
end
Environment variable inheritance problems cause runtime failures when deployed applications expect variables unavailable in Capistrano's execution context:
# Missing environment variables cause subtle failures
task :problematic_env do
on roles(:app) do
# This may fail if RAILS_ENV not properly set
within current_path do
execute 'bundle exec rails runner "puts Rails.env"'
end
end
end
# Explicit environment variable management
task :safe_env do
on roles(:app) do
within current_path do
# Load environment from multiple sources
env_vars = {
'RAILS_ENV' => fetch(:rails_env),
'NODE_ENV' => 'production'
}
# Source environment files if they exist
if test("[ -f #{shared_path}/.env ]")
env_vars.merge!(
Hash[capture("cat #{shared_path}/.env").split("\n").map { |line| line.split('=', 2) }]
)
end
with env_vars do
execute 'bundle exec rails runner "puts Rails.env"'
end
end
end
end
Deployment cleanup failures leave systems in inconsistent states. Failed deployments require explicit cleanup procedures:
namespace :deploy do
# Dangerous - leaves failed releases
task :deploy_without_cleanup do
invoke 'deploy:updating'
invoke 'deploy:updated'
invoke 'deploy:publishing' # This might fail
invoke 'deploy:published'
end
# Safe - cleanup on failure
task :deploy_with_cleanup do
begin
invoke 'deploy:updating'
invoke 'deploy:updated'
invoke 'deploy:publishing'
invoke 'deploy:published'
rescue => e
error "Deployment failed: #{e.message}"
# Cleanup failed release
on roles(:app) do
if test("[ -d #{release_path} ]")
execute 'rm', '-rf', release_path
info "Cleaned up failed release: #{release_path}"
end
# Verify current symlink still points to working release
if test("[ -L #{current_path} ]")
target = capture("readlink #{current_path}")
unless test("[ -d #{target} ]")
error "Current symlink points to missing directory: #{target}"
# Emergency rollback needed
end
end
end
raise
end
end
end
Concurrent deployment conflicts occur when multiple deployment processes target the same servers simultaneously. Deployment locking prevents conflicts:
namespace :deploy do
task :with_lock do
on roles(:app), limit: 1 do
lock_file = "#{fetch(:deploy_to)}/.deploy.lock"
lock_timeout = 1800 # 30 minutes
# Check for existing lock
if test("[ -f #{lock_file} ]")
lock_pid = capture("cat #{lock_file}").strip
lock_time = capture("stat -c %Y #{lock_file}").to_i
current_time = Time.now.to_i
if current_time - lock_time > lock_timeout
warn "Removing stale deployment lock (PID: #{lock_pid})"
execute 'rm', lock_file
else
raise "Deployment already in progress (PID: #{lock_pid})"
end
end
begin
# Create lock file with current process info
execute 'echo', "$$_#{Time.now.to_i}", '>', lock_file
# Proceed with deployment
yield
ensure
# Always remove lock
execute 'rm', '-f', lock_file
end
end
end
end
# Wrap standard deployment with locking
task :deploy do
invoke 'deploy:with_lock' do
invoke 'deploy:starting'
invoke 'deploy:started'
invoke 'deploy:updating'
invoke 'deploy:updated'
invoke 'deploy:publishing'
invoke 'deploy:published'
invoke 'deploy:finishing'
invoke 'deploy:finished'
end
end
Reference
Core Configuration Variables
Variable | Type | Default | Description |
---|---|---|---|
:application |
String |
nil | Application name for deployment identification |
:repo_url |
String |
nil | Source control repository URL |
:branch |
String |
'main' | Git branch or tag for deployment |
:deploy_to |
String |
nil | Remote deployment root directory |
:scm |
Symbol |
:git |
Source control management system |
:format |
Symbol |
:airbrussh |
Log output formatting style |
:log_level |
Symbol |
:debug |
Logging verbosity level |
:pty |
Boolean |
false |
Allocate pseudo-terminal for commands |
:keep_releases |
Integer |
5 | Number of releases to retain |
Path Variables
Variable | Type | Description |
---|---|---|
current_path |
Pathname |
Symlink to current release directory |
release_path |
Pathname |
Current deployment release directory |
releases_path |
Pathname |
Directory containing all releases |
shared_path |
Pathname |
Directory for shared resources |
repo_path |
Pathname |
Local repository cache directory |
Server Configuration
# Server definition with roles and properties
server 'hostname', user: 'deploy', roles: %w{role1 role2}, properties: {}
# SSH options per server
server 'hostname', ssh_options: {
port: 2222,
keys: %w(/path/to/key),
forward_agent: true,
user: 'deploy'
}
Task Definition Methods
Method | Parameters | Description |
---|---|---|
task(name, &block) |
name (Symbol), block (Proc) |
Define deployment task |
namespace(name, &block) |
name (Symbol), block (Proc) |
Group related tasks |
desc(description) |
description (String) |
Document task purpose |
before(task, hook) |
task (Symbol), hook (Symbol) |
Execute hook before task |
after(task, hook) |
task (Symbol), hook (Symbol) |
Execute hook after task |
Execution Context Methods
Method | Parameters | Returns | Description |
---|---|---|---|
on(servers, &block) |
servers (Array), block (Proc) |
void |
Execute block on specified servers |
roles(names) |
names (Array/Symbol) |
Array<Server> |
Get servers matching role names |
primary(role) |
role (Symbol) |
Server |
Get primary server for role |
execute(command, *args) |
command (String), args (Array) |
void |
Execute command on remote server |
capture(command, *args) |
command (String), args (Array) |
String |
Capture command output |
test(command, *args) |
command (String), args (Array) |
Boolean |
Test command exit status |
within(path, &block) |
path (String), block (Proc) |
void |
Execute block within directory |
with(env, &block) |
env (Hash), block (Proc) |
void |
Execute block with environment variables |
Built-in Tasks
Task | Description |
---|---|
deploy |
Complete deployment workflow |
deploy:check |
Verify deployment configuration |
deploy:rollback |
Rollback to previous release |
deploy:restart |
Restart application services |
deploy:cleanup |
Remove old releases |
deploy:cleanup_rollback |
Remove rollback release |
SCM Configuration
# Git-specific configuration
set :repo_url, 'git@github.com:user/repo.git'
set :branch, 'main'
set :git_shallow_clone, 1
# Subversion configuration
set :scm, :subversion
set :repo_url, 'https://svn.example.com/repo/trunk'
Error Classes
Class | Description |
---|---|
SSHKit::Runner::ExecuteError |
Command execution failure |
SSHKit::Command::Failed |
Command returned non-zero exit |
Net::SSH::AuthenticationFailed |
SSH authentication failure |
Capistrano::ValidationError |
Configuration validation error |