Deployment Automation
Master deployment automation with Xec's powerful execution engine. Learn to deploy applications reliably across local, remote, containerized, and cloud environments.
Overview
Xec transforms deployment automation by providing:
- Unified deployment API across all environments
- Built-in rollback capabilities
- Health checking and verification
- Zero-downtime deployment strategies
- Multi-environment orchestration
Basic Deployment Patterns
Simple File-Based Deployment
// deploy/simple-deploy.ts
import { $ } from '@xec-sh/core';
import { confirm } from '@clack/prompts';
async function simpleDeploy() {
const server = $.ssh({
host: 'app.example.com',
username: 'deploy',
privateKey: '~/.ssh/deploy_key'
});
console.log('📦 Starting deployment...');
// Build locally
await $`npm run build`;
// Create deployment directory
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const deployDir = `/app/releases/${timestamp}`;
await server`mkdir -p ${deployDir}`;
// Upload files
console.log('📤 Uploading files...');
await $`rsync -avz --exclude=node_modules ./dist/ deploy@app.example.com:${deployDir}/`;
// Update symlink
console.log('🔗 Updating application link...');
await server`ln -sfn ${deployDir} /app/current`;
// Restart service
console.log('🔄 Restarting service...');
await server`sudo systemctl restart app`;
// Verify
console.log('✅ Verifying deployment...');
await server`curl -f http://localhost:3000/health`;
console.log('✅ Deployment completed successfully!');
}
await simpleDeploy();
Git-Based Deployment
// deploy/git-deploy.ts
import { $ } from '@xec-sh/core';
interface GitDeployConfig {
repo: string;
branch: string;
deployPath: string;
postDeploy?: string;
}
async function gitDeploy(config: GitDeployConfig) {
const server = $.ssh({
host: process.env.DEPLOY_HOST!,
username: process.env.DEPLOY_USER!
});
console.log(`🌿 Deploying ${config.branch} to ${config.deployPath}`);
// Check if repo exists
const repoExists = await server`test -d ${config.deployPath}/.git`.nothrow();
if (repoExists.ok) {
// Pull latest changes
console.log('📥 Pulling latest changes...');
await server`cd ${config.deployPath} && git fetch origin`;
await server`cd ${config.deployPath} && git reset --hard origin/${config.branch}`;
} else {
// Clone repository
console.log('📋 Cloning repository...');
await server`git clone -b ${config.branch} ${config.repo} ${config.deployPath}`;
}
// Install dependencies
console.log('📦 Installing dependencies...');
await server`cd ${config.deployPath} && npm ci --production`;
// Run post-deploy script if provided
if (config.postDeploy) {
console.log('🔧 Running post-deploy script...');
await server`cd ${config.deployPath} && ${config.postDeploy}`;
}
console.log('✅ Git deployment completed!');
}
// Usage
await gitDeploy({
repo: 'git@github.com:example/app.git',
branch: 'main',
deployPath: '/app/production',
postDeploy: 'npm run migrate && pm2 restart app'
});
Zero-Downtime Deployments
Blue-Green Deployment
// deploy/blue-green.ts
import { $ } from '@xec-sh/core';
class BlueGreenDeployment {
private currentColor: 'blue' | 'green' = 'blue';
constructor(
private server: any,
private config: {
bluePath: string;
greenPath: string;
linkPath: string;
healthCheck: string;
}
) {}
async deploy() {
// Determine current and target colors
await this.detectCurrentColor();
const targetColor = this.currentColor === 'blue' ? 'green' : 'blue';
const targetPath = targetColor === 'blue'
? this.config.bluePath
: this.config.greenPath;
console.log(`🔄 Deploying to ${targetColor} environment`);
// Deploy to inactive environment
await this.deployToEnvironment(targetPath);
// Health check new deployment
await this.healthCheck(targetPath);
// Switch traffic
await this.switchTraffic(targetColor);
// Verify switch
await this.verifySwitch(targetColor);
console.log(`✅ Successfully switched to ${targetColor}`);
// Optional: cleanup old environment
const cleanup = await confirm({
message: 'Remove old deployment?'
});
if (cleanup) {
await this.cleanupEnvironment(this.currentColor);
}
}
private async detectCurrentColor() {
const result = await this.server`readlink ${this.config.linkPath}`.nothrow();
if (result.ok) {
this.currentColor = result.stdout.includes('blue') ? 'blue' : 'green';
}
console.log(`📍 Current environment: ${this.currentColor}`);
}
private async deployToEnvironment(path: string) {
console.log(`📦 Deploying to ${path}...`);
// Clean and prepare directory
await this.server`rm -rf ${path}`;
await this.server`mkdir -p ${path}`;
// Upload application
await $`rsync -avz ./dist/ ${this.server.config.username}@${this.server.config.host}:${path}/`;
// Install and build
await this.server`cd ${path} && npm ci --production`;
await this.server`cd ${path} && npm run migrate`;
}
private async healthCheck(path: string) {
console.log('🏥 Running health checks...');
// Start application in test mode
await this.server`cd ${path} && PORT=3001 npm start &`;
// Wait for startup
await new Promise(resolve => setTimeout(resolve, 5000));
// Check health
const maxAttempts = 30;
for (let i = 0; i < maxAttempts; i++) {
const result = await this.server`curl -f http://localhost:3001${this.config.healthCheck}`.nothrow();
if (result.ok) {
console.log('✅ Health check passed');
await this.server`pkill -f "PORT=3001"`;
return;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
throw new Error('Health check failed');
}
private async switchTraffic(targetColor: 'blue' | 'green') {
console.log(`🚦 Switching traffic to ${targetColor}...`);
const targetPath = targetColor === 'blue'
? this.config.bluePath
: this.config.greenPath;
// Update symlink atomically
await this.server`ln -sfn ${targetPath} ${this.config.linkPath}.tmp`;
await this.server`mv -Tf ${this.config.linkPath}.tmp ${this.config.linkPath}`;
// Reload web server
await this.server`sudo nginx -s reload`;
}
private async verifySwitch(targetColor: string) {
console.log('🔍 Verifying switch...');
const result = await this.server`curl -s http://localhost/version`;
if (!result.stdout.includes(targetColor)) {
throw new Error('Switch verification failed');
}
}
private async cleanupEnvironment(color: 'blue' | 'green') {
const path = color === 'blue'
? this.config.bluePath
: this.config.greenPath;
console.log(`🧹 Cleaning up ${color} environment...`);
await this.server`rm -rf ${path}`;
}
}
// Usage
const server = $.ssh({
host: 'prod.example.com',
username: 'deploy'
});
const deployment = new BlueGreenDeployment(server, {
bluePath: '/app/blue',
greenPath: '/app/green',
linkPath: '/app/current',
healthCheck: '/health'
});
await deployment.deploy();