You may be disapointed as me to find out that .env file doesn’t work with Docker Swarm as it does with Docker Compose. This limitation can be frustrating, but there are some approaches. A quick fix lazy approach and a more secure one that includes Docker secrets.
the quick fix lazy approach
Navigate to your directory containing both .env and docker-compose.yml, then run:
export $(cat .env) > /dev/null 2>&1; docker stack deploy <your_stack_name> --compose-file=docker-compose.yml
This command reads your .env file and sets all secrets as env variables before deploying the stack. The security concern is really that the env variables become available to all processes launched from that shell session, not just Docker. Any script or command you run afterward could potentially read those env variables.
the proper way
1. Create Docker Secrets
approach #1
Again, there’s more than one way to cook an egg. Here’s how I did it. Not very proud of it but it works. I know, I know. It’s not proper way if its simple or not secure “enough” too.
# disable shell history for current session
set +o history
echo "some_secret" | docker secret create db_password -
# re-enable history
set -o history
approach #2
Read from a file:
- Open a new file:
vim /tmp/db_passwordornano /tmp/db_password. - Enter the value, save and quit.
- Create the secret:
docker secret create db_password /tmp/db_password - Remove the file:
rm /tmp/db_password
approach #3
Read from an env variable:
- Run:
read -s -p "Enter DB password: " DB_PASSWORD - Enter the password.
- Validate the password:
echo $DB_PASSWORD - Create the secret:
echo $DB_PASSWORD | docker secret create db_password - - Unset the variable:
unset $DB_PASSWORD
approach #4
Just import it somehow from a secrets management tool. Doable, but haven’t tried it yet nor I have time to do so unfortunately. HashiCorps Vault and AWS Secrets Manager come to mind first.
2. Update Docker Compose config
If you don’t know how to prepare your config to work with Docker Swarm just check the previous blog which you can find at the bottom of this one.
Example:
service:
backend:
image: ...
secrets: - db_password
...
secrets:
db_password:
external: true
The source of the secret can be also a file or environment. A lot of things can go wrong for both of them including:
- files: exposure until manually removed, file permissions, distribute across swarm modes
- environment: process visibility, shell history, memory dumps, system-wide exposure
So stick with external as source, always.
3. Access secrets in your app
Docker mounts secrets as files in /run/secrets/. You have two main options for consuming them:
- Update your app code to read secrets from files e.g. in my our case
/run/secrets/db_password - Init scripts. That’s what I did. Actually I end up adding custom
ENTRYPOINTscript in the Dockerfile. For instance the following worked for me:
# Dockerfile
...
COPY init-swarm-secrets.sh /usr/local/bin/init-swarm-secrets.sh
RUN chmod +x /usr/local/bin/init-swarm-secrets.sh
ENTRYPOINT ["/usr/local/bin/init-swarm-secrets.sh"]
# init-swarm-secrets.sh
#!/bin/sh
[ -f "/run/secrets/db_password" ] && DB_PASSWORD=$(cat /run/secrets/db_password)
export DB_PASSWORD
# this last part is important to pass the command specified in Dockerfiles CMD
exec "$@"
bottom line
Probably I’m missing some approaches whatsoever. But you could always prompt for a solution for your specific scenario in case any of the described approaches didn’t work for you though.
Shameless plug: Check out justanotheruptime.com if you need quick monitoring uptime solution without the hustle.