Skip to main content

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();

Rolling Deployment​

// deploy/rolling-deploy.ts
import { $ } from '@xec-sh/core';

class RollingDeployment {
constructor(
private servers: string[],
private config: {
batchSize: number;
healthCheck: string;
gracePeriod: number;
}
) {}

async deploy() {
console.log(`🎯 Starting rolling deployment to ${this.servers.length} servers`);

const batches = this.createBatches();

for (let i = 0; i < batches.length; i++) {
console.log(`\nπŸ“¦ Deploying batch ${i + 1}/${batches.length}`);

await this.deployBatch(batches[i]);

if (i < batches.length - 1) {
console.log(`⏳ Waiting ${this.config.gracePeriod}s before next batch...`);
await new Promise(resolve => setTimeout(resolve, this.config.gracePeriod * 1000));
}
}

console.log('βœ… Rolling deployment completed successfully!');
}

private createBatches(): string[][] {
const batches: string[][] = [];

for (let i = 0; i < this.servers.length; i += this.config.batchSize) {
batches.push(this.servers.slice(i, i + this.config.batchSize));
}

return batches;
}

private async deployBatch(servers: string[]) {
// Remove servers from load balancer
await this.removeFromLoadBalancer(servers);

// Deploy to servers in parallel
await Promise.all(servers.map(server => this.deployToServer(server)));

// Health check all servers
await Promise.all(servers.map(server => this.healthCheckServer(server)));

// Add back to load balancer
await this.addToLoadBalancer(servers);
}

private async deployToServer(host: string) {
console.log(` πŸ“€ Deploying to ${host}...`);

const server = $.ssh({
host,
username: 'deploy'
});

// Stop service
await server`sudo systemctl stop app`;

// Update code
await $`rsync -avz ./dist/ deploy@${host}:/app/current/`;

// Update dependencies
await server`cd /app/current && npm ci --production`;

// Run migrations
await server`cd /app/current && npm run migrate`;

// Start service
await server`sudo systemctl start app`;

console.log(` βœ… ${host} deployed`);
}

private async healthCheckServer(host: string) {
console.log(` πŸ₯ Health checking ${host}...`);

const server = $.ssh({
host,
username: 'deploy'
});

const maxAttempts = 30;
for (let i = 0; i < maxAttempts; i++) {
const result = await server`curl -f http://localhost:3000${this.config.healthCheck}`.nothrow();

if (result.ok) {
console.log(` βœ… ${host} healthy`);
return;
}

await new Promise(resolve => setTimeout(resolve, 2000));
}

throw new Error(`Health check failed for ${host}`);
}

private async removeFromLoadBalancer(servers: string[]) {
console.log(' πŸ”„ Removing from load balancer...');

for (const server of servers) {
await $`aws elb deregister-instances-from-load-balancer \
--load-balancer-name prod-lb \
--instances ${server}`;
}

// Wait for connections to drain
await new Promise(resolve => setTimeout(resolve, 30000));
}

private async addToLoadBalancer(servers: string[]) {
console.log(' πŸ”„ Adding to load balancer...');

for (const server of servers) {
await $`aws elb register-instances-with-load-balancer \
--load-balancer-name prod-lb \
--instances ${server}`;
}
}
}

// Usage
const deployment = new RollingDeployment(
['prod1.example.com', 'prod2.example.com', 'prod3.example.com', 'prod4.example.com'],
{
batchSize: 2,
healthCheck: '/health',
gracePeriod: 60
}
);

await deployment.deploy();

Container Deployments​

Docker Deployment​

// deploy/docker-deploy.ts
import { $ } from '@xec-sh/core';

async function dockerDeploy() {
const image = 'myapp:latest';
const container = 'myapp-prod';
const registry = 'registry.example.com';

console.log('🐳 Starting Docker deployment...');

// Build image
console.log('πŸ”¨ Building Docker image...');
await $`docker build -t ${image} .`;

// Tag for registry
await $`docker tag ${image} ${registry}/${image}`;

// Push to registry
console.log('πŸ“€ Pushing to registry...');
await $`docker push ${registry}/${image}`;

// Deploy to servers
const servers = ['prod1.example.com', 'prod2.example.com'];

for (const host of servers) {
console.log(`\nπŸš€ Deploying to ${host}...`);

const server = $.ssh({
host,
username: 'deploy'
});

// Pull latest image
await server`docker pull ${registry}/${image}`;

// Stop old container
await server`docker stop ${container} || true`;
await server`docker rm ${container} || true`;

// Start new container
await server`docker run -d \
--name ${container} \
--restart unless-stopped \
-p 3000:3000 \
-e NODE_ENV=production \
-v /app/data:/data \
${registry}/${image}`;

// Health check
await server`docker exec ${container} curl -f http://localhost:3000/health`;

console.log(`βœ… ${host} deployment successful`);
}

console.log('\nβœ… Docker deployment completed!');
}

await dockerDeploy();

Docker Compose Deployment​

// deploy/compose-deploy.ts
import { $ } from '@xec-sh/core';

async function composeDeploy() {
const server = $.ssh({
host: 'prod.example.com',
username: 'deploy'
});

console.log('🐳 Docker Compose deployment...');

// Upload compose file
await $`scp docker-compose.prod.yml deploy@prod.example.com:/app/docker-compose.yml`;

// Upload environment file
await $`scp .env.production deploy@prod.example.com:/app/.env`;

// Pull latest images
console.log('πŸ“₯ Pulling latest images...');
await server`cd /app && docker-compose pull`;

// Deploy with zero downtime
console.log('πŸ”„ Starting new containers...');
await server`cd /app && docker-compose up -d --no-deps --scale app=2 app`;

// Wait for health
await new Promise(resolve => setTimeout(resolve, 10000));

// Remove old containers
await server`cd /app && docker-compose up -d --no-deps --remove-orphans app`;

// Cleanup
await server`docker system prune -f`;

console.log('βœ… Compose deployment complete!');
}

await composeDeploy();

Kubernetes Deployment​

// deploy/k8s-deploy.ts
import { $ } from '@xec-sh/core';

class KubernetesDeployment {
constructor(
private namespace: string,
private deployment: string
) {}

async deploy(image: string) {
console.log(`☸️ Deploying to Kubernetes...`);
console.log(`πŸ“ Namespace: ${this.namespace}`);
console.log(`πŸš€ Deployment: ${this.deployment}`);
console.log(`🐳 Image: ${image}`);

// Update deployment image
await this.updateImage(image);

// Monitor rollout
await this.monitorRollout();

// Verify deployment
await this.verifyDeployment();

console.log('βœ… Kubernetes deployment successful!');
}

private async updateImage(image: string) {
console.log('πŸ“¦ Updating deployment image...');

await $`kubectl set image deployment/${this.deployment} \
${this.deployment}=${image} \
-n ${this.namespace} \
--record`;
}

private async monitorRollout() {
console.log('⏳ Monitoring rollout...');

const result = await $`kubectl rollout status \
deployment/${this.deployment} \
-n ${this.namespace} \
--timeout=10m`.nothrow();

if (!result.ok) {
console.error('❌ Rollout failed, initiating rollback...');
await this.rollback();
throw new Error('Deployment failed and was rolled back');
}
}

private async rollback() {
await $`kubectl rollout undo \
deployment/${this.deployment} \
-n ${this.namespace}`;

await $`kubectl rollout status \
deployment/${this.deployment} \
-n ${this.namespace} \
--timeout=5m`;
}

private async verifyDeployment() {
console.log('πŸ” Verifying deployment...');

// Check pod status
const pods = await $`kubectl get pods \
-n ${this.namespace} \
-l app=${this.deployment} \
-o json`;

const podData = JSON.parse(pods.stdout);
const runningPods = podData.items.filter(
pod => pod.status.phase === 'Running'
);

if (runningPods.length === 0) {
throw new Error('No running pods found');
}

console.log(`βœ… ${runningPods.length} pods running`);

// Run smoke test
const testPod = runningPods[0].metadata.name;
const k8s = $.k8s({
pod: testPod,
namespace: this.namespace
});

await k8s`curl -f http://localhost:3000/health`;
console.log('βœ… Health check passed');
}
}

// Usage
const deployment = new KubernetesDeployment('production', 'myapp');
await deployment.deploy('myapp:v1.2.3');

Cloud Platform Deployments​

AWS ECS Deployment​

// deploy/aws-ecs.ts
import { $ } from '@xec-sh/core';

async function ecsD
eploy() {
const cluster = 'prod-cluster';
const service = 'myapp-service';
const taskDefinition = 'myapp-task';
const image = 'myapp:latest';

console.log('☁️ AWS ECS Deployment');

// Build and push to ECR
console.log('πŸ”¨ Building image...');
await $`docker build -t ${image} .`;

// Get ECR login
await $`aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
123456789.dkr.ecr.us-east-1.amazonaws.com`;

// Tag and push
const ecrImage = `123456789.dkr.ecr.us-east-1.amazonaws.com/${image}`;
await $`docker tag ${image} ${ecrImage}`;
await $`docker push ${ecrImage}`;

// Update task definition
console.log('πŸ“ Updating task definition...');
const taskDef = await $`aws ecs describe-task-definition \
--task-definition ${taskDefinition} \
--query 'taskDefinition' \
--output json`;

const newTaskDef = JSON.parse(taskDef.stdout);
newTaskDef.containerDefinitions[0].image = ecrImage;
delete newTaskDef.taskDefinitionArn;
delete newTaskDef.revision;
delete newTaskDef.status;
delete newTaskDef.requiresAttributes;
delete newTaskDef.compatibilities;
delete newTaskDef.registeredAt;
delete newTaskDef.registeredBy;

await $`echo '${JSON.stringify(newTaskDef)}' | \
aws ecs register-task-definition \
--cli-input-json file:///dev/stdin`;

// Update service
console.log('πŸ”„ Updating service...');
await $`aws ecs update-service \
--cluster ${cluster} \
--service ${service} \
--task-definition ${taskDefinition}`;

// Wait for deployment
console.log('⏳ Waiting for deployment...');
await $`aws ecs wait services-stable \
--cluster ${cluster} \
--services ${service}`;

console.log('βœ… ECS deployment complete!');
}

await ecsDeploy();

Google Cloud Run Deployment​

// deploy/gcloud-run.ts
import { $ } from '@xec-sh/core';

async function cloudRunDeploy() {
const project = 'my-project';
const service = 'myapp';
const region = 'us-central1';
const image = `gcr.io/${project}/${service}`;

console.log('☁️ Google Cloud Run Deployment');

// Build with Cloud Build
console.log('πŸ”¨ Building with Cloud Build...');
await $`gcloud builds submit \
--tag ${image} \
--project ${project}`;

// Deploy to Cloud Run
console.log('πŸš€ Deploying to Cloud Run...');
await $`gcloud run deploy ${service} \
--image ${image} \
--platform managed \
--region ${region} \
--project ${project} \
--allow-unauthenticated \
--memory 512Mi \
--cpu 1 \
--max-instances 10 \
--min-instances 1`;

// Get service URL
const url = await $`gcloud run services describe ${service} \
--platform managed \
--region ${region} \
--project ${project} \
--format 'value(status.url)'`;

console.log(`βœ… Deployed to: ${url.stdout.trim()}`);

// Verify deployment
const health = await $`curl -f ${url.stdout.trim()}/health`;
console.log('βœ… Health check passed');
}

await cloudRunDeploy();

Azure App Service Deployment​

// deploy/azure-app.ts
import { $ } from '@xec-sh/core';

async function azureAppDeploy() {
const resourceGroup = 'myapp-rg';
const appName = 'myapp-prod';
const plan = 'myapp-plan';

console.log('☁️ Azure App Service Deployment');

// Build application
console.log('πŸ”¨ Building application...');
await $`npm run build`;

// Create deployment package
await $`zip -r deploy.zip . -x "*.git*" -x "node_modules/*"`;

// Deploy to Azure
console.log('πŸš€ Deploying to Azure...');
await $`az webapp deployment source config-zip \
--resource-group ${resourceGroup} \
--name ${appName} \
--src deploy.zip`;

// Restart app
await $`az webapp restart \
--resource-group ${resourceGroup} \
--name ${appName}`;

// Wait for startup
await new Promise(resolve => setTimeout(resolve, 30000));

// Verify deployment
const url = await $`az webapp show \
--resource-group ${resourceGroup} \
--name ${appName} \
--query defaultHostName \
--output tsv`;

const health = await $`curl -f https://${url.stdout.trim()}/health`;
console.log('βœ… Azure deployment complete!');

// Cleanup
await $`rm deploy.zip`;
}

await azureAppDeploy();

Deployment Strategies​

Canary Deployment​

// deploy/canary.ts
import { $ } from '@xec-sh/core';

class CanaryDeployment {
constructor(
private config: {
service: string;
newVersion: string;
stages: Array<{
percentage: number;
duration: number;
metrics: string[];
}>;
}
) {}

async deploy() {
console.log(`🐀 Starting canary deployment of ${this.config.newVersion}`);

for (const stage of this.config.stages) {
console.log(`\nπŸ“Š Stage: ${stage.percentage}% traffic`);

// Update traffic split
await this.updateTrafficSplit(stage.percentage);

// Monitor metrics
const success = await this.monitorMetrics(stage);

if (!success) {
console.error('❌ Canary failed, rolling back...');
await this.rollback();
throw new Error('Canary deployment failed');
}

console.log(`βœ… Stage ${stage.percentage}% successful`);
}

// Full rollout
await this.updateTrafficSplit(100);
console.log('βœ… Canary deployment complete!');
}

private async updateTrafficSplit(percentage: number) {
console.log(`πŸ”„ Routing ${percentage}% traffic to new version...`);

await $`kubectl patch virtualservice ${this.config.service} \
--type merge \
-p '{"spec":{"http":[{"route":[
{"destination":{"host":"${this.config.service}","subset":"stable"},"weight":${100 - percentage}},
{"destination":{"host":"${this.config.service}","subset":"canary"},"weight":${percentage}}
]}]}}'`;
}

private async monitorMetrics(stage: any): Promise<boolean> {
console.log(`πŸ“ˆ Monitoring for ${stage.duration} minutes...`);

const startTime = Date.now();
const endTime = startTime + stage.duration * 60 * 1000;

while (Date.now() < endTime) {
for (const metric of stage.metrics) {
const result = await this.checkMetric(metric);

if (!result) {
console.error(`❌ Metric ${metric} failed`);
return false;
}
}

// Check every 30 seconds
await new Promise(resolve => setTimeout(resolve, 30000));
}

return true;
}

private async checkMetric(metric: string): Promise<boolean> {
// Query Prometheus for metric
const query = encodeURIComponent(`rate(http_requests_total{status=~"5.."}[5m])`);
const result = await $`curl -s http://prometheus:9090/api/v1/query?query=${query}`;

const data = JSON.parse(result.stdout);
const errorRate = parseFloat(data.data.result[0]?.value[1] || '0');

// Fail if error rate > 1%
return errorRate < 0.01;
}

private async rollback() {
await this.updateTrafficSplit(0);
await $`kubectl delete deployment ${this.config.service}-canary`;
}
}

// Usage
const canary = new CanaryDeployment({
service: 'myapp',
newVersion: 'v2.0.0',
stages: [
{ percentage: 10, duration: 5, metrics: ['error_rate', 'latency'] },
{ percentage: 25, duration: 10, metrics: ['error_rate', 'latency'] },
{ percentage: 50, duration: 15, metrics: ['error_rate', 'latency'] },
{ percentage: 75, duration: 10, metrics: ['error_rate', 'latency'] }
]
});

await canary.deploy();

Feature Flag Deployment​

// deploy/feature-flag.ts
import { $ } from '@xec-sh/core';

async function featureFlagDeploy() {
const feature = 'new-checkout-flow';
const percentage = process.env.ROLLOUT_PERCENTAGE || '10';

console.log(`🚩 Deploying with feature flag: ${feature}`);

// Deploy new code (feature is behind flag)
await $`npm run deploy:production`;

// Gradually enable feature
const stages = [10, 25, 50, 75, 100];

for (const percent of stages) {
console.log(`\nπŸ“Š Enabling for ${percent}% of users...`);

// Update feature flag
await $`curl -X PATCH https://flags.example.com/api/flags/${feature} \
-H "Authorization: Bearer $FLAG_API_KEY" \
-d '{"percentage": ${percent}}'`;

// Monitor metrics
console.log('πŸ“ˆ Monitoring metrics...');
await new Promise(resolve => setTimeout(resolve, 300000)); // 5 minutes

// Check error rates
const metrics = await $`curl https://metrics.example.com/api/errors?feature=${feature}`;
const errorRate = JSON.parse(metrics.stdout).error_rate;

if (errorRate > 0.01) {
console.error('❌ High error rate detected, disabling feature...');

await $`curl -X PATCH https://flags.example.com/api/flags/${feature} \
-H "Authorization: Bearer $FLAG_API_KEY" \
-d '{"enabled": false}'`;

throw new Error('Feature deployment failed');
}

console.log(`βœ… ${percent}% rollout successful`);
}

console.log('βœ… Feature fully deployed!');
}

await featureFlagDeploy();

Rollback Strategies​

Automated Rollback​

// deploy/auto-rollback.ts
import { $ } from '@xec-sh/core';

class AutoRollbackDeployment {
private previousVersion?: string;
private deployed = false;

constructor(
private config: {
service: string;
version: string;
healthEndpoint: string;
metricsEndpoint: string;
rollbackThreshold: {
errorRate: number;
responseTime: number;
};
}
) {}

async deploy() {
try {
// Store current version
await this.saveCurrentVersion();

// Deploy new version
await this.deployNewVersion();
this.deployed = true;

// Monitor for issues
await this.monitorDeployment();

console.log('βœ… Deployment successful!');
} catch (error) {
if (this.deployed) {
await this.performRollback();
}
throw error;
}
}

private async saveCurrentVersion() {
const result = await $`kubectl get deployment ${this.config.service} \
-o jsonpath='{.spec.template.spec.containers[0].image}'`;

this.previousVersion = result.stdout.trim();
console.log(`πŸ“Œ Current version: ${this.previousVersion}`);
}

private async deployNewVersion() {
console.log(`πŸš€ Deploying ${this.config.version}...`);

await $`kubectl set image deployment/${this.config.service} \
${this.config.service}=${this.config.version}`;

await $`kubectl rollout status deployment/${this.config.service} \
--timeout=5m`;
}

private async monitorDeployment() {
console.log('πŸ“Š Monitoring deployment health...');

const monitoringDuration = 10 * 60 * 1000; // 10 minutes
const checkInterval = 30 * 1000; // 30 seconds
const endTime = Date.now() + monitoringDuration;

while (Date.now() < endTime) {
// Check health
const healthOk = await this.checkHealth();
if (!healthOk) {
throw new Error('Health check failed');
}

// Check metrics
const metricsOk = await this.checkMetrics();
if (!metricsOk) {
throw new Error('Metrics threshold exceeded');
}

await new Promise(resolve => setTimeout(resolve, checkInterval));
}
}

private async checkHealth(): Promise<boolean> {
const result = await $`curl -f ${this.config.healthEndpoint}`.nothrow();
return result.ok;
}

private async checkMetrics(): Promise<boolean> {
const metrics = await $`curl ${this.config.metricsEndpoint}`;
const data = JSON.parse(metrics.stdout);

const errorRate = data.error_rate || 0;
const responseTime = data.response_time || 0;

if (errorRate > this.config.rollbackThreshold.errorRate) {
console.error(`❌ Error rate ${errorRate} exceeds threshold`);
return false;
}

if (responseTime > this.config.rollbackThreshold.responseTime) {
console.error(`❌ Response time ${responseTime}ms exceeds threshold`);
return false;
}

return true;
}

private async performRollback() {
console.log('βͺ Performing automatic rollback...');

if (!this.previousVersion) {
throw new Error('No previous version to rollback to');
}

await $`kubectl set image deployment/${this.config.service} \
${this.config.service}=${this.previousVersion}`;

await $`kubectl rollout status deployment/${this.config.service} \
--timeout=5m`;

console.log('βœ… Rollback completed');
}
}

// Usage
const deployment = new AutoRollbackDeployment({
service: 'myapp',
version: 'myapp:v2.0.0',
healthEndpoint: 'http://myapp.example.com/health',
metricsEndpoint: 'http://metrics.example.com/api/myapp',
rollbackThreshold: {
errorRate: 0.01, // 1% error rate
responseTime: 1000 // 1 second
}
});

await deployment.deploy();

Deployment Configuration​

Environment-Based Config​

// deploy/config.ts
interface DeploymentConfig {
environment: string;
host: string;
deployPath: string;
buildCommand: string;
startCommand: string;
healthCheck: string;
rollbackEnabled: boolean;
}

const configs: Record<string, DeploymentConfig> = {
development: {
environment: 'development',
host: 'dev.example.com',
deployPath: '/app/dev',
buildCommand: 'npm run build:dev',
startCommand: 'npm run dev',
healthCheck: '/health',
rollbackEnabled: false
},
staging: {
environment: 'staging',
host: 'staging.example.com',
deployPath: '/app/staging',
buildCommand: 'npm run build:staging',
startCommand: 'npm run start',
healthCheck: '/health',
rollbackEnabled: true
},
production: {
environment: 'production',
host: 'prod.example.com',
deployPath: '/app/production',
buildCommand: 'npm run build:prod',
startCommand: 'npm run start',
healthCheck: '/health',
rollbackEnabled: true
}
};

export function getDeployConfig(env: string): DeploymentConfig {
const config = configs[env];

if (!config) {
throw new Error(`Unknown environment: ${env}`);
}

return config;
}

// Usage in deployment script
const env = process.env.DEPLOY_ENV || 'staging';
const config = getDeployConfig(env);

const server = $.ssh({
host: config.host,
username: 'deploy'
});

await $`${config.buildCommand}`;
await server`cd ${config.deployPath} && ${config.startCommand}`;

Best Practices​

1. Pre-Deployment Checks​

async function preDeploymentChecks() {
console.log('πŸ” Running pre-deployment checks...');

// Check git status
const gitStatus = await $`git status --porcelain`;
if (gitStatus.stdout.trim()) {
throw new Error('Uncommitted changes detected');
}

// Run tests
await $`npm test`;

// Check dependencies
await $`npm audit --audit-level=high`;

// Verify build
await $`npm run build`;

console.log('βœ… Pre-deployment checks passed');
}

2. Deployment Notifications​

async function notifyDeployment(status: 'started' | 'success' | 'failed', details?: any) {
const webhook = process.env.SLACK_WEBHOOK;

if (!webhook) return;

const message = {
started: 'πŸš€ Deployment started',
success: 'βœ… Deployment successful',
failed: '❌ Deployment failed'
};

await $`curl -X POST ${webhook} \
-H 'Content-Type: application/json' \
-d '{"text": "${message[status]}", "details": ${JSON.stringify(details)}}'`;
}

3. Deployment Logging​

class DeploymentLogger {
private logs: any[] = [];

log(level: string, message: string, data?: any) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
data
};

this.logs.push(entry);
console.log(`[${level}] ${message}`);
}

async save() {
const filename = `deploy-${Date.now()}.log`;
await $`echo '${JSON.stringify(this.logs, null, 2)}' > logs/${filename}`;
}
}

Troubleshooting​

Common Issues​

  1. Permission Denied

    // Ensure deploy user has necessary permissions
    await server`sudo -n true 2>/dev/null || echo "Need sudo access"`;
  2. Port Already in Use

    // Kill process using port
    await server`fuser -k 3000/tcp || true`;
  3. Disk Space Issues

    // Clean up old deployments
    await server`find /app/releases -maxdepth 1 -mtime +30 -exec rm -rf {} \\;`;

Next Steps​

Summary​

You've learned how to:

  • βœ… Implement basic file and Git-based deployments
  • βœ… Create zero-downtime deployment strategies
  • βœ… Deploy to containers and Kubernetes
  • βœ… Implement canary and feature flag deployments
  • βœ… Set up automatic rollback mechanisms
  • βœ… Deploy to cloud platforms
  • βœ… Monitor and log deployments

Continue to test automation to learn about automating your test suite.