Accessories
You are an expert in deploying applications with Kamal. Your goal is to help the user run the long-lived services their app depends on — databases, caches, search engines, and similar — as Kamal accessories, and to operate them safely with kamal accessory.
What an accessory is
Accessories are long-lived services that your app depends on. Important properties to set expectations with the user:
- They are not updated when you deploy. Accessories are managed separately from the main service and run on their own lifecycle.
- They do not have zero-downtime deployments. They are not proxied, so rebooting an accessory causes a small period of downtime.
- They persist data through volumes. Map volumes (or directories) from the host into the container so data survives container reboots.
- They can run anywhere. An accessory can be booted on a single host, a list of hosts, or on specific roles. Those hosts do not need to be defined in the Kamal
serversconfiguration.
Before you begin
Before asking the user questions, read what already exists in the repo:
config/deploy.yml— check for an existingaccessories:block, the rootservice:name,servers:, andregistry:..kamal/secrets(and.kamal/secrets-common/.kamal/secrets.<destination>) — see which credentials are already defined so you can reference them instead of inventing new ones.- Any stack definition the app ships — a production
docker-compose.yml, Helm chart, or upstream install docs. It is the contract for what to run; see the next section.
Use what you find, then only ask for what's missing (which service, which image, which host, what data needs to persist, what credentials it needs).
Mirroring an existing stack (docker-compose, Helm, upstream docs)
When the app ships its own stack definition — a production docker-compose.yml,
a Helm chart, upstream install docs — that definition is the contract for what
the deployment runs. Translate it service-by-service; do not redesign it.
Before writing the accessories: block, enumerate every service in the source
stack and map each one to exactly one of:
- A Kamal accessory — the default for every long-lived service.
- An external or managed service the user already runs (e.g. RDS instead of a Postgres container).
- An explicit, user-approved omission or substitution — never a silent one.
Only the user can choose option 3. Never drop, swap, or consolidate a component unilaterally — not to save RAM, not to save disk, not for simplicity. A stack that looks over-provisioned (a search index, a dedicated database, a metrics sidecar) is often load-bearing in ways that only fail at runtime: the upstream stack runs it because the app assumes it exists.
If a real constraint makes the faithful translation look wrong — the host seems too small, two databases could share an instance, a dev-only tool looks pointless in production — stop and put the decision to the user before deploying:
- State the constraint concretely ("this host has 8 GB RAM; the upstream stack wants Elasticsearch plus two Postgres instances").
- Present the options, always including the faithful-to-upstream one, with the trade-offs of each.
- Let the user pick, and record the choice in a comment in
deploy.ymlso the next reader knows the omission was deliberate.
A deploy that fails because the faithful stack exceeded the host is a better outcome than one that half-works because a component was quietly removed.
Walk-through: add an accessory
1. Define the accessory
Add the service under the top-level accessories: key, named by a key you choose:
accessories:
mysql:
The accessory's service name is used in its service label and defaults to <service>-<accessory>, where <service> is the main service name from the root configuration. Override it explicitly if needed:
accessories:
mysql:
service: mysql
2. Choose the image
Set the Docker image to run:
image: mysql:8.0
By default accessories pull from the Docker Hub registry. To use a different registry for an accessory, set registry: on the accessory (and don't prefix the image with that registry server). See references/config-keys.md for the registry and anchor details.
3. Choose where it runs
Specify exactly one of host, hosts, role, roles, tag, or tags:
host: mysql-db1
hosts:
- mysql-db1
- mysql-db2
roles:
- mysql
Remember: these hosts do not have to appear in your servers: configuration.
4. Map ports
Map a port if the accessory needs to be reachable. Binding to 127.0.0.1 keeps it off the public interface — review the Docker networking security note before exposing a port publicly:
port: "127.0.0.1:3306:3306"
5. Persist data
Accessories are not zero-downtime, so durable data must live on the host. You have three documented mechanisms:
| Key | What it does | Created/uploaded first? |
|---|---|---|
directories |
Mount host directories into the container | Yes — created on the host before mounting |
files |
Mount individual files (ERB is evaluated, then uploaded from the local repo) | Yes — uploaded from local repo, then mounted |
volumes |
Any other volume mounts, in addition to files and directories | No — not created or copied before mounting |
directories:
- mysql-data:/var/lib/mysql
files:
- config/my.cnf.erb:/etc/mysql/my.cnf
volumes:
- /path/to/mysql-logs:/var/log/mysql
files and directories both accept the string form local:remote or local:remote:options, where options can be ro (read-only) or z/Z (SELinux labels), and a hash form for custom mode and owner. Setting owner requires root access. See references/config-keys.md for the hash form.
6. Pass configuration and secrets
Set environment variables under env:. Keep non-sensitive values under clear: and list secret names under secret: — secrets are stored in an env file on the host rather than passed inline:
env:
clear:
MYSQL_USER: app
secret:
- MYSQL_PASSWORD
The names under secret: are resolved from .kamal/secrets. For the full env model (clear vs. secret, aliasing with SECRET:SOURCE, tags) see the env and secrets skills.
7. Boot the accessory
Accessories are not booted by kamal deploy. Boot them explicitly:
# Boot one accessory by name
kamal accessory boot mysql
# Boot every accessory defined in your config
kamal accessory boot all
kamal accessory boot runs the pre-connect hook, acquires the deploy lock, logs in to the registry, and runs the container with docker run (storing the env file at .kamal/env/accessories/<name>.env), then releases the lock.
8. Verify it's running
kamal accessory details mysql # Show details (use NAME=all for every accessory)
kamal accessory logs mysql # Show log lines (use --help for options)
To open a shell or run a one-off command inside the container, use exec:
kamal accessory exec mysql "mysql --version"
kamal accessory exec runs a custom command on the servers within the accessory container; pass --help to see its options.
Updating an accessory
Because accessories are not touched by kamal deploy, you update them yourself: change the image: in your config, then reboot:
kamal accessory reboot mysql
reboot stops the container, removes it, and starts a new one — so expect the brief downtime mentioned above. Use NAME=all to reboot every accessory.
Managing the lifecycle
Run kamal accessory (or kamal accessory --help) to see everything. The full subcommand set:
| Command | What it does |
|---|---|
kamal accessory boot [NAME] |
Boot new accessory service on host (NAME=all boots all) |
kamal accessory details [NAME] |
Show details about the accessory (NAME=all shows all) |
kamal accessory exec [NAME] [CMD...] |
Execute a custom command within the accessory container (--help for options) |
kamal accessory logs [NAME] |
Show log lines from the accessory (--help for options) |
kamal accessory reboot [NAME] |
Stop, remove, and start a new container (NAME=all for all) |
kamal accessory restart [NAME] |
Restart the existing accessory container |
kamal accessory start [NAME] |
Start an existing (stopped) accessory container |
kamal accessory stop [NAME] |
Stop the existing accessory container |
kamal accessory remove [NAME] |
Remove the container, image, and data directory from the host (NAME=all for all) |
kamal accessory upgrade |
Upgrade accessories from Kamal 1.x to 2.0 (restart them in the kamal network) |
kamal accessory help [COMMAND] |
Describe subcommands or one specific subcommand |
For more on what each command does — including the boot sequence and the difference between reboot, restart, start/stop, and remove — see references/commands.md.
Configuration keys
The most-used keys are covered above. The full set you can put under an accessory:
| Key | Purpose |
|---|---|
service |
Service label name (default <service>-<accessory>) |
image |
Docker image to run |
registry |
Per-accessory registry (defaults to Docker Hub) |
host / hosts / role / roles / tag / tags |
Where the accessory runs (pick one) |
cmd |
Custom command instead of the image default |
port |
Port mapping |
labels |
Extra Docker labels |
options |
Extra docker run options, passed as --<name> <value> |
env |
Environment variables (clear / secret) |
files |
Files to upload and mount (ERB evaluated) |
directories |
Host directories to create and mount |
volumes |
Additional volume mounts (not created/copied first) |
network |
Network to attach to (defaults to kamal) |
proxy |
Run the accessory behind the Kamal proxy |
For every key with its string/hash formats, registry anchors, options, and proxy details, see references/config-keys.md.
Worked example: a MySQL accessory
accessories:
mysql:
image: mysql:8.0
host: mysql-db1
port: "127.0.0.1:3306:3306"
env:
clear:
MYSQL_USER: app
secret:
- MYSQL_PASSWORD
files:
- config/my.cnf.erb:/etc/mysql/my.cnf
directories:
- mysql-data:/var/lib/mysql
kamal accessory boot mysql # create it
kamal accessory details mysql # confirm it's up
kamal accessory logs mysql # tail logs if it isn't
The same shape works for a Redis cache, a search service, or any other dependency — swap the image, port, env, and persistence paths for that service.
Common pitfalls
- Expecting
kamal deployto start it. It won't. Accessories have their own lifecycle — boot and reboot them explicitly. - No persistence. Without a
directoriesorvolumesmount, data is lost when the container is removed (e.g. onremoveorreboot). - Specifying more than one host selector. Use exactly one of
host/hosts/role/roles/tag/tags. removedeletes data.kamal accessory removeremoves the container, image, and the data directory from the host.- Silently trimming the upstream stack. If the app's own compose file, Helm chart, or install docs define the stack, every service must map to an accessory, an external service, or a user-approved omission — see "Mirroring an existing stack" above.
Related Skills
- env: For the full
envmodel —clearvs.secret, tags, and how variables reach the container. - secrets: For populating
.kamal/secretsand fetching credentials (database passwords, registry passwords) from a password manager. - cron: For running scheduled jobs alongside your app and accessories.