Xamal is an Elixir port of Kamal — Basecamp's tool for deploying web apps anywhere. It uses Mix tasks, Elixir configuration (config/xamal.exs), native releases, and Caddy instead of Docker containers and kamal-proxy.
If you're familiar with Kamal, you should feel right at home. The operational model, hook system, secrets management, and destination-based multi-environment workflow carry over.
What's different from Kamal
- Elixir releases instead of Docker containers — built with
mix release, distributed as tarballs - Caddy instead of kamal-proxy — automatic TLS via Let's Encrypt, zero-downtime blue-green deploys via port switching
- Erlang SSH instead of shelling out to
ssh— connection pooling via GenServer - Mix tasks — deploy from the same toolchain that builds your release
Docker-specific configuration (image, registry, Dockerfile, build args, etc.) is intentionally omitted since releases replace containers entirely.
Requirements
Locally, where you run the Mix tasks:
- Elixir 1.15+ / OTP 26+ and an environment that can build a release (
mix release) - SSH access to your target servers
On each target server:
- A systemd-based Linux host with SSH enabled
- Caddy —
mix xamal.server.bootstrap(run as part ofmix xamal.setup) installs Caddy and the systemd service unit for you if they are not already present
Install
Add Xamal as a Mix dependency in the application you deploy:
# mix.exs
defp deps do
[
{:xamal, "~> 0.3", only: [:dev, :test], runtime: false}
]
endThen fetch dependencies:
mix deps.get
Documentation is available on HexDocs.
Upgrading
Upgrading from 0.2.0? The escript binary, the xamal <cmd> interface, and
config/deploy.yml were all replaced in 0.3.0. See UPGRADING.md
for step-by-step migration instructions.
Quick start
# Generate config stubs, sample hooks, release config, and helper aliases
mix xamal.init
# Edit config/xamal.exs and .xamal/secrets, then:
mix xamal.setup
Xamal ships built-in reference docs for every config section. Run mix xamal.docs
to list the available topics, or mix xamal.docs <topic> (e.g. mix xamal.docs servers)
for details.
Configuration
Xamal reads Elixir config from config/xamal.exs:
import Config
config :xamal,
service: "my-app",
servers: [
web: ["192.168.0.1", "192.168.0.2"],
worker: [
hosts: ["192.168.0.3"],
cmd: ~s(bin/my_app eval "Worker.start()")
]
],
ssh: [
user: "deploy"
],
caddy: [
host: "app.example.com",
app_port: 4000
],
env: [
clear: [
PHX_HOST: "app.example.com"
],
secret: ["SECRET_KEY_BASE"]
],
release: [
name: "my_app",
mix_env: "prod"
],
health_check: [
path: "/health"
]Important: The release.name must match a named release in your mix.exs. Xamal runs mix release <name>, which requires an explicit release definition:
# mix.exs
def project do
[
...
releases: [
my_app: [
version: {:from_app, :my_app}
]
]
]
endWithout this, mix release my_app will fail with Unknown release :my_app.
Because this is Elixir config, normal Elixir expressions such as System.get_env/1 are available. Use regular Mix aliases in your application's mix.exs for command shortcuts.
Run mix xamal.docs <topic> for detailed reference on any config section.
Commands
Run mix help | grep xamal to list every available task.
Deploy
mix xamal.setup # Bootstrap servers and deploy
mix xamal.deploy # Build, distribute, and boot
mix xamal.redeploy # Deploy without bootstrapping
mix xamal.rollback VERSION # Roll back to a previous version
mix xamal.prune # Remove old releases, keeping the retained count
mix xamal.remove # Remove remote release and proxy resourcesApp
mix xamal.app.boot # Zero-downtime restart
mix xamal.app.start # Start the service on its active port (no swap)
mix xamal.app.stop # Stop application services
mix xamal.app.exec CMD # Run a command in the release context
mix xamal.app.logs -f # Tail application logs
mix xamal.app.version # Show the current deployed version per host
mix xamal.app.stale_releases # Preview releases that pruning would remove
mix xamal.app.maintenance # Enable maintenance mode (503)
mix xamal.app.live # Disable maintenance mode
mix xamal.shell # Open a remote shell on the running release
mix xamal.iex # Open a remote IEx session
mix xamal.migrate # Run the release migrator (<App>.Release.migrate)Inspect
mix xamal.versions # List release versions on servers
mix xamal.details # Show app and proxy status
mix xamal.audit # Show the audit log
mix xamal.version # Print the installed Xamal versionBuild, server, and lock
mix xamal.build.deliver # Build and upload release
mix xamal.build.push # Build release tarball
mix xamal.build.pull # Upload release tarball
mix xamal.build.details # Print build configuration
mix xamal.server.bootstrap # Bootstrap target servers
mix xamal.server.exec CMD # Run a shell command on servers
mix xamal.server.logs # Show Caddy/proxy logs from servers
mix xamal.lock.status # Check deploy lock
mix xamal.lock.acquire # Acquire deploy lock
mix xamal.lock.release # Release deploy lockConfig, docs, and secrets
mix xamal.config # Show merged configuration
mix xamal.docs hooks # Show hook documentation
mix xamal.secrets.print # Show secrets (redacted)
mix xamal.secrets.extract KEY # Print one secret value
mix xamal.secrets.fetch ADAPTER [OPTIONS]Hooks
Shell scripts in .xamal/hooks/ that run locally at lifecycle points:
| Hook | When |
|---|---|
pre-build | Before building the release |
post-build | After building the release |
pre-deploy | Before deploying |
post-deploy | After deploying |
pre-app-boot | Before booting the app |
post-app-boot | After booting the app |
pre-caddy-reload | Before Caddy config reload |
post-caddy-reload | After Caddy config reload |
Hooks receive environment variables like XAMAL_SERVICE, XAMAL_VERSION, XAMAL_HOSTS, XAMAL_PERFORMER, etc. Run mix xamal.docs hooks for the full list.
Destinations
Multi-environment deploys work the same as Kamal:
mix xamal.deploy -d staging
mix xamal.deploy -d production
With override files like config/xamal/staging.exs and secrets in .xamal/secrets.staging.
License
MIT