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:

  1. Build the Docker image
  2. Distribute it to servers (registry push or direct transfer)
  3. 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}
  1. Image is pushed to the registry
  2. Servers pull the image
  3. 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:

  1. Deploy to app1, wait for healthy
  2. Deploy to app2, wait for healthy
  3. 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
Previous
Environment variables