Multi-Server Deployments
Deploy your application to multiple servers with a single configuration file. Perfect for multi-environment setups, geographic distribution, and A/B testing.
Use Cases
- Multi-environment deployments: Production, staging, and development
- Geographic distribution: Deploy to multiple regions with geo-based load balancing
- A/B testing: Deploy different versions to separate infrastructure
- Gradual rollouts: Test in staging before promoting to production
Basic Multi-Target Configuration
name: "my-app"
# Base configuration inherited by all targets
image:
repository: "ghcr.io/your-username/my-app"
tag: "latest"
acme_email: "you@email.com"
targets:
production:
server: production.haloy.com
image:
tag: "v1.2.3" # Override with stable release
domains: - domain: "my-app.com"
replicas: 3
staging:
server: staging.haloy.com
image:
tag: "main" # Use latest main branch
domains: - domain: "staging.my-app.com"
replicas: 1
name: "my-app"
# Base configuration inherited by all targets
image:
repository: "ghcr.io/your-username/my-app"
tag: "latest"
acme_email: "you@email.com"
targets:
production:
server: production.haloy.com
image:
tag: "v1.2.3" # Override with stable release
domains: - domain: "my-app.com"
replicas: 3
staging:
server: staging.haloy.com
image:
tag: "main" # Use latest main branch
domains: - domain: "staging.my-app.com"
replicas: 1
Deploying to Targets
Deploy to Specific Target
# Deploy to production only
haloy deploy --target production
# Short form
haloy deploy -t staging
# Deploy to production only
haloy deploy --target production
# Short form
haloy deploy -t staging
Deploy to All Targets
haloy deploy --all
haloy deploy --all
Configuration Inheritance
Base configuration provides defaults that targets can override:
name: "my-app"
# Base configuration - inherited by all targets
image:
repository: "ghcr.io/my-org/my-app"
tag: "latest"
replicas: 2
port: "8080"
env:
- name: "LOG_LEVEL"
value: "info"
targets:
production:
# Inherits: replicas=2, port="8080", base env (LOG_LEVEL)
# Overrides: image.tag, adds domains, adds/overrides env variables
server: "prod.haloy.com"
image:
tag: "v1.2.3"
domains:
- domain: "my-app.com"
env:
- name: "NODE_ENV"
value: "production"
- name: "LOG_LEVEL"
value: "warn" # Overrides base LOG_LEVEL
# Base LOG_LEVEL would be inherited if not overridden
staging:
# Inherits: image (repo + tag), port="8080", base env (LOG_LEVEL)
# Overrides: server, replicas, adds domains
server: "staging.haloy.com"
replicas: 1
domains:
- domain: "staging.my-app.com"
# Inherits all base env variables (LOG_LEVEL="info")
name: "my-app"
# Base configuration - inherited by all targets
image:
repository: "ghcr.io/my-org/my-app"
tag: "latest"
replicas: 2
port: "8080"
env:
- name: "LOG_LEVEL"
value: "info"
targets:
production:
# Inherits: replicas=2, port="8080", base env (LOG_LEVEL)
# Overrides: image.tag, adds domains, adds/overrides env variables
server: "prod.haloy.com"
image:
tag: "v1.2.3"
domains:
- domain: "my-app.com"
env:
- name: "NODE_ENV"
value: "production"
- name: "LOG_LEVEL"
value: "warn" # Overrides base LOG_LEVEL
# Base LOG_LEVEL would be inherited if not overridden
staging:
# Inherits: image (repo + tag), port="8080", base env (LOG_LEVEL)
# Overrides: server, replicas, adds domains
server: "staging.haloy.com"
replicas: 1
domains:
- domain: "staging.my-app.com"
# Inherits all base env variables (LOG_LEVEL="info")
Inheritance Rules:
- Base values provide defaults for all targets
- Most target values completely override base values (no merging)
- Only specified fields in targets override the base
- Exception: Environment variables (
env) are merged - target values override base values with the same name, while preserving other base variables - Other arrays (like
domains,volumes) are replaced entirely, not merged
Multi-Environment Example
Complete setup for production, staging, and development:
name: "my-app"
# Base configuration
image:
repository: "ghcr.io/my-org/my-app"
tag: "latest"
replicas: 1
port: "8080"
acme_email: "devops@my-app.com"
# Global hooks run once before/after all deployments
global_pre_deploy:
- "echo 'Starting deployment pipeline'"
- "npm run build"
global_post_deploy:
- "echo 'All deployments completed'"
- "curl -X POST https://status.io/webhook"
targets:
production:
server: prod.haloy.com
image:
tag: "v1.2.3"
domains: - domain: "my-app.com"
aliases: - "www.my-app.com"
replicas: 3
env: - name: "NODE_ENV"
value: "production" - name: "DATABASE_URL"
from:
secret: "onepassword:prod-db.url"
pre_deploy: - "npm run migrate:production"
post_deploy: - "echo 'Production deployed'"
staging:
server: staging.haloy.com
image:
tag: "main"
domains: - domain: "staging.my-app.com"
replicas: 2
env: - name: "NODE_ENV"
value: "staging" - name: "DATABASE_URL"
from:
env: "STAGING_DATABASE_URL"
pre_deploy: - "npm run migrate:staging"
development:
server: dev.haloy.com
image:
tag: "develop"
domains: - domain: "dev.my-app.com"
replicas: 1
env: - name: "NODE_ENV"
value: "development" - name: "DEBUG"
value: "true" - name: "DATABASE_URL"
from:
env: "DEV_DATABASE_URL"
name: "my-app"
# Base configuration
image:
repository: "ghcr.io/my-org/my-app"
tag: "latest"
replicas: 1
port: "8080"
acme_email: "devops@my-app.com"
# Global hooks run once before/after all deployments
global_pre_deploy:
- "echo 'Starting deployment pipeline'"
- "npm run build"
global_post_deploy:
- "echo 'All deployments completed'"
- "curl -X POST https://status.io/webhook"
targets:
production:
server: prod.haloy.com
image:
tag: "v1.2.3"
domains: - domain: "my-app.com"
aliases: - "www.my-app.com"
replicas: 3
env: - name: "NODE_ENV"
value: "production" - name: "DATABASE_URL"
from:
secret: "onepassword:prod-db.url"
pre_deploy: - "npm run migrate:production"
post_deploy: - "echo 'Production deployed'"
staging:
server: staging.haloy.com
image:
tag: "main"
domains: - domain: "staging.my-app.com"
replicas: 2
env: - name: "NODE_ENV"
value: "staging" - name: "DATABASE_URL"
from:
env: "STAGING_DATABASE_URL"
pre_deploy: - "npm run migrate:staging"
development:
server: dev.haloy.com
image:
tag: "develop"
domains: - domain: "dev.my-app.com"
replicas: 1
env: - name: "NODE_ENV"
value: "development" - name: "DEBUG"
value: "true" - name: "DATABASE_URL"
from:
env: "DEV_DATABASE_URL"
Geographic Distribution
Deploy to multiple regions:
name: "my-api"
image:
repository: "ghcr.io/my-org/api"
tag: "v2.0.0"
targets:
us-east:
server: us-east.haloy.com
domains: - domain: "us-api.my-app.com"
replicas: 2
env: - name: "REGION"
value: "us-east-1" - name: "DATABASE_URL"
from:
secret: "onepassword:us-db.url"
eu-west:
server: eu-west.haloy.com
domains: - domain: "eu-api.my-app.com"
replicas: 2
env: - name: "REGION"
value: "eu-west-1" - name: "DATABASE_URL"
from:
secret: "onepassword:eu-db.url"
asia-pacific:
server: ap-southeast.haloy.com
domains: - domain: "ap-api.my-app.com"
replicas: 2
env: - name: "REGION"
value: "ap-southeast-1" - name: "DATABASE_URL"
from:
secret: "onepassword:ap-db.url"
name: "my-api"
image:
repository: "ghcr.io/my-org/api"
tag: "v2.0.0"
targets:
us-east:
server: us-east.haloy.com
domains: - domain: "us-api.my-app.com"
replicas: 2
env: - name: "REGION"
value: "us-east-1" - name: "DATABASE_URL"
from:
secret: "onepassword:us-db.url"
eu-west:
server: eu-west.haloy.com
domains: - domain: "eu-api.my-app.com"
replicas: 2
env: - name: "REGION"
value: "eu-west-1" - name: "DATABASE_URL"
from:
secret: "onepassword:eu-db.url"
asia-pacific:
server: ap-southeast.haloy.com
domains: - domain: "ap-api.my-app.com"
replicas: 2
env: - name: "REGION"
value: "ap-southeast-1" - name: "DATABASE_URL"
from:
secret: "onepassword:ap-db.url"
Deployment Hooks
Global Hooks
Run once before or after all target deployments:
name: "my-app"
global_pre_deploy:
- "npm run build"
- "docker build -t my-app ."
- "npm run test"
global_post_deploy:
- "npm run cleanup"
- "echo 'All deployments complete'"
targets:
production:
server: prod.haloy.com
staging:
server: staging.haloy.com
name: "my-app"
global_pre_deploy:
- "npm run build"
- "docker build -t my-app ."
- "npm run test"
global_post_deploy:
- "npm run cleanup"
- "echo 'All deployments complete'"
targets:
production:
server: prod.haloy.com
staging:
server: staging.haloy.com
Target-Specific Hooks
Run before or after individual target deployments:
targets:
production:
server: prod.haloy.com
pre_deploy:
- "npm run migrate:production"
- "npm run cache:clear:production"
post_deploy:
- "curl https://my-app.com/api/health"
- "echo 'Production health check passed'"
staging:
server: staging.haloy.com
pre_deploy: - "npm run migrate:staging"
post_deploy: - "npm run test:e2e:staging"
targets:
production:
server: prod.haloy.com
pre_deploy:
- "npm run migrate:production"
- "npm run cache:clear:production"
post_deploy:
- "curl https://my-app.com/api/health"
- "echo 'Production health check passed'"
staging:
server: staging.haloy.com
pre_deploy: - "npm run migrate:staging"
post_deploy: - "npm run test:e2e:staging"
Target-Specific Commands
All commands support target selection:
# Check status of specific target
haloy status --target production
haloy status -t staging
# View logs from specific target
haloy logs --target production
# Stop specific target
haloy stop --target staging
# Rollback specific target
haloy rollback --target production <deployment-id>
# Apply to all targets
haloy status --all
haloy stop --all
# Check status of specific target
haloy status --target production
haloy status -t staging
# View logs from specific target
haloy logs --target production
# Stop specific target
haloy stop --target staging
# Rollback specific target
haloy rollback --target production <deployment-id>
# Apply to all targets
haloy status --all
haloy stop --all
Managing Multiple Servers
Add all your servers to the local client:
# Add production server
haloy server add production.haloy.com <prod-token>
# Add staging server
haloy server add staging.haloy.com <staging-token>
# Add development server
haloy server add dev.haloy.com <dev-token>
# List all configured servers
haloy server list
# Remove a server
haloy server delete staging.haloy.com
# Add production server
haloy server add production.haloy.com <prod-token>
# Add staging server
haloy server add staging.haloy.com <staging-token>
# Add development server
haloy server add dev.haloy.com <dev-token>
# List all configured servers
haloy server list
# Remove a server
haloy server delete staging.haloy.com
Build Once, Deploy Everywhere
When using the image builder, the image is built once and distributed to all targets:
name: "my-app"
image:
repository: "my-app"
tag: "latest"
builder:
context: "."
dockerfile: "Dockerfile"
# Build once before all deployments
global_pre_deploy:
- "npm run test"
targets:
production:
server: prod.haloy.com
image:
builder:
push: "server" # Upload to production server
staging:
server: staging.haloy.com
image:
builder:
push: "server" # Upload to staging server
name: "my-app"
image:
repository: "my-app"
tag: "latest"
builder:
context: "."
dockerfile: "Dockerfile"
# Build once before all deployments
global_pre_deploy:
- "npm run test"
targets:
production:
server: prod.haloy.com
image:
builder:
push: "server" # Upload to production server
staging:
server: staging.haloy.com
image:
builder:
push: "server" # Upload to staging server
Best Practices
- Use version tags for production: Pin to specific versions like
v1.2.3 - Use branch tags for staging: Track branches like
mainordevelop - Separate secrets per environment: Different credentials for each target
- Test in staging first: Deploy to staging before production
- Use global hooks for builds: Build once, deploy everywhere
- Document target purposes: Add comments explaining each target
Troubleshooting
Target Not Found
Ensure the target name matches exactly:
haloy deploy --target production # Correct
haloy deploy --target prod # Wrong if target is named "production"
haloy deploy --target production # Correct
haloy deploy --target prod # Wrong if target is named "production"
Server Not Configured
Add the server if you see authentication errors:
haloy server add <server-domain> <api-token>
haloy server add <server-domain> <api-token>
Different Results Per Target
Use --show-resolved-config to see the final configuration per target:
haloy validate-config --show-resolved-config
haloy validate-config --show-resolved-config