I recently started using Kamal to manage my Docker deployments and I’ve found it to be a really great tool. Here’s what my architecture looks like:
A few days ago Kamal released a 1.0 version which changed how it handles environment variables. Previously, Kamal
would read a .env
file and use that to populate the environment variables for the Docker container. I didn’t
like this approach so I was using Ansible to generate ENV files on the server and have Kamal run the docker
container with a custom --env-file
argument.
With the new release, Kamal now takes the same approach, it generates an env file and pushes it to the server
then runs the container with the --env-file
argument. This is a much better approach and I’m glad to see it.
However, this still leaves the problem of where to store the secrets. To generate the env file Kamal supports using a Ruby ERB template file which is great because it allows you to run shell commands within the template.
Combing this with 1Password means you can extract secrets from 1Password and use them in your env file:
MY_SECRET_ENV_VAR=<%= `op read "op://Vault/Secret Env/password" -n` %>
This is a great solution and it documented in the Kamal docs but it does have a few drawbacks. Mainly, it’s a bit verbose and it can also be quite slow to retrieve all the secrets if you have a lot. Also, adding a new var requires updates in 3 places.
The solution I came up with was to store all the secrets in a single 1Password item and fetch them all at once
with op get item
instead of op read
. This works because op get item
returns YAML compatible output.
So now my template looks like this:
<% vars = YAML.load(`op item get --vault "My Vault" "App Secrets" --fields type=string,type=concealed`) %>
<% for var in vars %>
<% if var.key?("Value") %>
<%= var['Label'] %>=<%= var['Value'] %>
<% end %>
<% end %>
Adding new fields to the 1Password item doesn’t require any changes to the template.