BunShip includes a production-ready Docker setup with a multi-stage Dockerfile, development and production Compose files, and health checks for every service.
The development Compose file (docker/docker-compose.yml) starts three services: the API, a background worker, and Redis.
Copy
# Build and start all servicesdocker-compose -f docker/docker-compose.yml up --build# Start in the backgrounddocker-compose -f docker/docker-compose.yml up -d# View API logsdocker-compose -f docker/docker-compose.yml logs -f api# Stop everythingdocker-compose -f docker/docker-compose.yml down
In development mode, source directories are mounted as read-only volumes so code changes reflect without rebuilding:
The production override file (docker/docker-compose.prod.yml) layers on top of the development file to add resource limits, replica counts, log rotation, and Redis authentication.
Copy
# Build the production imagedocker build -f docker/Dockerfile.api -t bunship-api:latest .# Start with production settingsdocker-compose \ -f docker/docker-compose.yml \ -f docker/docker-compose.prod.yml \ up -d
The db-data volume is only relevant when using a file-based SQLite database
(TURSO_DATABASE_URL=file:../../local.db). In production with Turso Cloud, no local database
volume is needed.
Copy
# List volumesdocker volume ls | grep bunship# Back up Redis datadocker run --rm -v bunship_redis-data:/data -v $(pwd):/backup alpine \ tar czf /backup/redis-backup.tar.gz -C /data .# Remove volumes (destroys data)docker-compose -f docker/docker-compose.yml down -v
Inside Docker Compose, service-level environment entries override values from env_file. The Compose files set REDIS_HOST=redis so the API connects to the Redis container by service name rather than localhost.
# Run 3 API instancesdocker-compose \ -f docker/docker-compose.yml \ -f docker/docker-compose.prod.yml \ up -d --scale api=3
When running multiple instances, place a reverse proxy (Nginx, Caddy, or Traefik) in front to distribute traffic. BunShip is stateless — sessions are validated via JWT and jobs are coordinated through Redis — so any instance can handle any request.
# Confirm Redis is healthydocker-compose -f docker/docker-compose.yml ps redis# Test connection from inside the networkdocker-compose -f docker/docker-compose.yml exec api \ bun -e "const r = require('ioredis'); new r('redis://redis:6379').ping().then(console.log)"