Deployment
Deploying applications
Deploy your Docker containers with zero-downtime rolling updates.
Basic deployment
Deploy your application with a single command:
odysseus deploy --image v1.0.0
This assumes your image is already available (pushed to a registry or present on servers).
Build and deploy
Build the image and deploy in one step:
odysseus deploy --build --image v1.0.0
Odysseus will:
- Build the Docker image
- Distribute it to servers (registry push or direct transfer)
- Deploy with zero-downtime
Deployment process
For each server and role, Odysseus follows this sequence:
1. Start new container
Starting container myapp-web-v1.0.0...
The new container starts alongside the existing one.
2. Health check
Waiting for health check...
Container healthy after 5s
Odysseus waits for Docker's health check (or a configurable timeout).
3. Update routing (web only)
Adding container to Caddy upstream...
For web roles, Odysseus adds the new container to Caddy's load balancer.
4. Drain old container
Removing old container from upstream...
Waiting for connections to drain...
The old container is removed from the load balancer and given time to finish existing requests.
5. Stop and cleanup
Stopping old container...
Cleanup complete. Kept 2 most recent containers.
Old containers are stopped. Odysseus keeps the 2 most recent for quick rollbacks.
Deployment options
--config
Specify a custom configuration file:
odysseus deploy --config production.yml --image v1.0.0
--image
Docker image tag to deploy:
odysseus deploy --image v1.0.0
odysseus deploy --image latest
odysseus deploy --image abc123 # Git SHA
--build
Build the image before deploying:
odysseus deploy --build --image v1.0.0
--dry-run
Show what would happen without making changes:
odysseus deploy --dry-run --image v1.0.0
--verbose
Show detailed SSH commands:
odysseus deploy --verbose --image v1.0.0
Image distribution
Odysseus supports two methods for getting images to servers.
Registry mode
When registry.server is configured:
registry:
server: ghcr.io
username: myuser
password: ${GITHUB_TOKEN}
- Image is pushed to the registry
- Servers pull the image
- Deployment proceeds
odysseus deploy --build --image v1.0.0
# Builds → Pushes to registry → Servers pull → Deploy
Pussh mode (no registry)
Without a registry, use pussh for direct transfer:
odysseus pussh --build --image v1.0.0
odysseus deploy --image v1.0.0
Or combine with deploy:
odysseus deploy --build --image v1.0.0
Odysseus detects the absence of a registry and uses direct SSH transfer.
Rollback
If something goes wrong, deploy a previous version:
odysseus deploy --image v0.9.0
Odysseus keeps the 2 most recent containers on each server, so rollback is instant if the old image is still present.
For faster rollbacks to the previous version:
# See what containers are available
odysseus containers your-server
# Deploy the previous version
odysseus deploy --image previous-tag
Deployment status
Check the status of your deployment:
odysseus status your-server
Output includes:
- Running containers by role
- Caddy routes and SSL status
- Accessory services
Multi-server deployments
With multiple servers:
servers:
web:
hosts:
- app1.example.com
- app2.example.com
- app3.example.com
Odysseus deploys to each server sequentially:
- Deploy to app1, wait for healthy
- Deploy to app2, wait for healthy
- Deploy to app3, wait for healthy
This ensures your application remains available throughout the deployment.
CI/CD integration
GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
- run: gem install odysseus-cli
- run: odysseus deploy --build --image ${{ github.sha }}
env:
ODYSSEUS_MASTER_KEY: ${{ secrets.ODYSSEUS_MASTER_KEY }}
GitLab CI
deploy:
stage: deploy
image: ruby:3.2
script:
- gem install odysseus-cli
- odysseus deploy --build --image $CI_COMMIT_SHA
only:
- main
Troubleshooting
Container won't start
Check logs:
odysseus logs your-server --role web -n 100
Health check failing
Verify your health endpoint:
odysseus app exec your-server --command "curl -v localhost:3000/health"
Deployment stuck
Use verbose mode to see what's happening:
odysseus deploy --verbose --image v1.0.0