Zero-Config, Zero-Downtime deployments to your Linux server
Mushak is a CLI tool that brings PaaS-like deployment experience to your own Linux VPS. Deploy your Docker-based applications with a simple git push, complete with automatic builds, health checks, and zero-downtime switching.
If your project has a Dockerfile or docker-compose.yml, it should just work.
Tip
Click the image above to watch the full demo video.
I absolutly love Docker and Docker Compose. I just wish it was easy to deploy it to a remote server. But there are 2 issues when it comes to remote deployment:
- You need script or some kind of automation to get docker compose up and running on a remote server and then configure env. etc.
- You will face downtime when redeployen a new version of your app.
These 2 isses is why Mushak exists. It is a zero-config, zero-downtime alternative to self hosted PaaS you find on the market.
So its basically GIT + Caddy + Docker (Compose) = Mushak.
- Zero Configuration: Works out-of-the-box with
Dockerfileordocker-compose.yml - Zero Downtime: Uses Caddy reverse proxy for atomic traffic switching
- Multi-App Support: Deploy multiple apps on the same server
- Smart Builds: Automatically detects and handles both Dockerfile and Docker Compose projects
- Health Checks: Ensures new deployments are healthy before switching traffic
- Automatic Cleanup: Manages old containers and deployment artifacts
- Branch Control: Configure which Git branch triggers deployments
- Self-Updating: Built-in update mechanism
- Local Machine: Git installed
- Server: Fresh Linux server (Ubuntu strongly recommended) with SSH access
- Domain: DNS pointing to your server (for HTTPS via Caddy)
- Project: Application with
Dockerfileordocker-compose.yml
# Install via curl
curl -sL https://raw.githubusercontent.com/hmontazeri/mushak/main/install.sh | shgit clone https://github.com/hmontazeri/mushak.git
cd mushak
go build -o mushak ./cmd/mushak
sudo mv mushak /usr/local/bin/Download the latest binary from GitHub Releases
In your project directory:
mushak init [email protected]Mushak will prompt you for:
- Domain: Your app's domain (e.g.,
app.example.com) - App name: Defaults to your directory name (press Enter to use default)
This will:
- Install Docker, Git, and Caddy on your server
- Set up a Git repository
- Configure deployment hooks
- Add a Git remote named
mushak
mushak deployYour app will be built, health-checked, and deployed with zero downtime!
Visit https://app.example.com (make sure DNS is configured)
- Git Push:
mushak deploypushes your code to the server's bare Git repository - Hook Triggered: A post-receive hook on the server starts the deployment process
- Build: Detects Dockerfile or docker-compose.yml and builds your app
- Port Assignment: Finds a free port (8000-9000) for the new container
- Health Check: Polls the health endpoint (default:
/) for up to 30 seconds - Traffic Switch: Updates Caddy configuration to point to the new container
- Cleanup: Stops and removes old containers
# Port your app listens on inside the container
internal_port: 3000
# Health check endpoint
health_path: /api/health
# Health check timeout in seconds
health_timeout: 60Initialize a new app on the server.
mushak init [email protected]Mushak will interactively prompt for domain and app name. You can also provide them as flags:
mushak init [email protected] --domain app.com --app myapp --branch mainFlags:
--host: Server address (user@hostname)--domain: Domain name for the app (required)--app: App name (default: current directory name)--branch: Git branch to deploy (default: main)--key: SSH key path (default: ~/.ssh/id_rsa)--port: SSH port (default: 22)
Deploy the current branch to the server.
mushak deployFlags:
-f, --force: Force push to server
Remove an app from the server.
mushak destroyFlags:
--host: Server address (optional if config exists)--user: SSH username (optional if config exists)--app: App name (optional if config exists)--force: Skip confirmation prompt
Update mushak to the latest version.
mushak updateFlags:
--check: Check for updates without installing
Show the current version.
mushak versionTrigger a redeployment of the current version.
mushak redeployList running containers for the application.
mushak containersStream logs from your application.
mushak logsFlags:
-f, --follow: Follow log output-n, --lines: Number of lines to show (default: 100)-c, --container: Filter logs by container name
Open an interactive shell in your application container.
mushak execMushak provides several commands to manage environment variables:
Priority system:
- First checks for
.env.prodon the server - Falls back to
.envif.env.proddoesn't exist - Creates
.env.prodby default if neither exists - During deployment, copies the environment file to each release
Commands:
# Set individual variables and redeploy
mushak env set DATABASE_PASSWORD=secret RAILS_MASTER_KEY=abc123
# Upload entire .env file
mushak env push # Auto-detects .env.prod, .env.production, or .env
mushak env push .env.prod # Upload specific file
mushak env push --deploy # Upload and immediately redeploy
# Download from server
mushak env pull # Downloads to local .env.prod
# Compare local vs server
mushak env diff # Shows differencesFROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]# mushak.yaml
internal_port: 3000
health_path: /healthThis example shows a complete setup with web server, background worker, and database using environment variables.
# docker-compose.yml
services:
postgres:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DATABASE_USERNAME:-postgres}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: myapp_production
# Don't expose ports externally - services communicate internally
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
web:
build: .
depends_on:
postgres:
condition: service_healthy
env_file:
- .env.prod
environment:
DATABASE_HOST: postgres
DATABASE_PORT: 5432
RAILS_ENV: production
# Don't specify ports - Mushak handles this dynamically
volumes:
- app_storage:/app/storage
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
worker:
build: .
command: bundle exec sidekiq
depends_on:
postgres:
condition: service_healthy
env_file:
- .env.prod
environment:
DATABASE_HOST: postgres
DATABASE_PORT: 5432
RAILS_ENV: production
volumes:
- app_storage:/app/storage
volumes:
postgres_data:
app_storage:# mushak.yaml
internal_port: 3000
health_path: /health
health_timeout: 60# .env.prod (gitignored)
DATABASE_PASSWORD=your-secure-password
DATABASE_USERNAME=postgres
RAILS_MASTER_KEY=your-master-key
SECRET_KEY_BASE=your-secret-keyHow Mushak handles this setup:
- Service Detection: Mushak automatically detects the
webservice for routing (ignorespostgresandworker) - Port Mapping: Creates a dynamic port mapping (e.g., 8000:3000) only for the
webservice - Environment Files:
- During
mushak init, prompts to upload your local.env.prod - During deployment, copies
/var/www/myapp/.env.prodto each release directory - All services can access the variables via
env_file: .env.prod
- During
- Service Startup Order:
postgresstarts first, waits for health checkwebandworkerstart after postgres is healthy- Only
webis exposed via Caddy reverse proxy
- Internal Communication: Services communicate via internal Docker network (no exposed ports needed)
- Smart Redeployments:
- Infrastructure services (
postgres) are automatically detected and kept running - Application services (
web,worker) are rebuilt and restarted - Database stays up, no connection drops, no data loss
- Infrastructure services (
Deployment flow:
# Initialize (auto-uploads .env.prod if exists)
mushak init [email protected]
# Domain: myapp.com
# App name [myapp]:
# ✓ Found local .env.prod with 4 variables (DATABASE_PASSWORD, RAILS_MASTER_KEY, +2 more)
# → Upload to server? [Y/n]: y
# ✓ Uploaded .env.prod to server
# First Deploy
mushak deploy
# → Environment file: .env.prod ✓
# → Detecting build method...
# Service name: web (detected web service)
# Infrastructure services: postgres
# Application services: web worker
# → Building and starting containers...
# postgres: started ✓
# web: building... healthy ✓
# worker: building... running ✓
# Second Deploy (after code changes)
mushak deploy
# → Environment file: .env.prod ✓
# Infrastructure services: postgres
# Application services: web worker
# → Ensuring infrastructure services are running...
# postgres: already running ✓ (not restarted)
# → Building and deploying application services...
# web: building... healthy ✓
# worker: building... running ✓Managing environment variables:
# Update variables and redeploy
mushak env set DATABASE_PASSWORD=new-password
# Upload entire .env.prod file
mushak env push
# Download from server (for team sync)
mushak env pull
# Compare local vs server
mushak env diffImportant notes:
- No port conflicts: Don't specify
ports:in your docker-compose.yml for the web service. Mushak dynamically assigns ports (8000-9000 range) to avoid conflicts between deployments. - Service naming: Services with "web" in the name are automatically detected. If you use a different name, create a
mushak.yamlwithservice_name: your-service. - Container naming: Avoid setting
container_name:in docker-compose.yml. Let Mushak manage naming for proper isolation between deployments. If you must use custom names,mushak logsandmushak shellwill still work. - Volume persistence: Docker volumes are ALWAYS preserved across deployments. Mushak never uses
docker compose down -v, so database data, uploaded files, and other persistent storage remain intact even after multiple deployments. - Zero-downtime: Old containers keep running until new ones pass health checks, then Mushak switches traffic and cleans up old containers (but keeps volumes).
Deploy multiple apps on the same server:
# App 1
cd ~/projects/app1
mushak init [email protected] --domain app1.com
# App 2
cd ~/projects/app2
mushak init [email protected] --domain app2.comEach app gets its own:
- Git repository
- Deployment directory
- Caddy configuration
- Port assignment
/var/repo/
├── app1.git/ # Bare Git repo
├── app2.git/
/var/www/
├── app1/
│ ├── abc123/ # Deployment by commit SHA
│ └── def456/
├── app2/
/etc/caddy/
├── Caddyfile # Main config (imports apps)
└── apps/
├── app1.caddy # Per-app reverse proxy config
└── app2.caddy
Check the output from mushak deploy. Common issues:
- Health check timeout (increase in
mushak.yaml) - Port already in use (mushak will find another)
- Build errors (fix Dockerfile/docker-compose.yml)
- Verify SSH access:
ssh [email protected] - Check SSH key path:
--key ~/.ssh/id_rsa - Ensure server allows SSH connections
- Verify DNS points to your server:
dig app.example.com - Check Caddy is running:
ssh [email protected] sudo systemctl status caddy - View Caddy logs:
ssh [email protected] sudo journalctl -u caddy -f
- Uses SSH key-based authentication
- Server requires passwordless sudo for Docker and Caddy operations
- HTTPS automatically configured by Caddy
- Containers run in isolated Docker networks
- GitHub Actions integration
- Rollback to previous deployment
- Log viewing (
mushak logs) - SSH access (
mushak exec) - Environment variable management (
mushak env set) - Database migrations support
- Custom health check commands
Contributions are welcome! Please open an issue or submit a pull request.
MIT License - see LICENSE file for details
Built with:
- Cobra - CLI framework
- Caddy - Reverse proxy
- Docker - Containerization
- go-github-selfupdate - Self-update
- GitHub Issues: github.com/hmontazeri/mushak/issues
- Documentation: github.com/hmontazeri/mushak/wiki
Launched your app with Mushak and need server/container monitoring? Check out BareAgent.io - a simple, effective monitoring solution for your Docker containers and servers.
By using BareAgent.io, you're directly supporting the development of Mushak and other open-source tools. Thank you for your support!