Don't Forget to Trim Your Docker Secrets

  • #Bun
  • #Docker

While working on my current side project, I read about the recommendation to pass secrets to Docker containers using Docker secrets. Until now, I have been passing API keys, passwords, and other sensitive information as environment variables. So I thought this would be a good opportunity to learn about Docker secrets and how to use them.

Depicting the concept of Docker secrets. In the foreground, there is a secure digital vault with a combination lock, symbolizing security. A whale, representing Docker, is positioned above the vault. Surrounding the vault are various cloud icons and security-related symbols, illustrating cloud-based security and data protection.
Leveraging Docker Secrets as the Vault for Your Sensitive Data

What are Docker Secrets?

Use Docker secrets to securely pass sensitive information to your containers. Secrets are stored in encrypted form and are only available at runtime to services that have been granted access to them.

This reminds me of how hosted services like Supabase or Vercel handle secrets. You define the secret in advance and then reference it in your configuration. But instead of being stored in an environment variable, they are available as a file in the container (e.g. /run/secrets/my_secret).

Availability

This feature is only available in Docker Swarm or with Docker Compose.

Reading Secrets

You can read secrets from a file in your application code. For example, in a Bun application, you can read the secret from a file like this:

db.ts
let DB_PASSWORD: string | undefined
const password = await Bun.file(Bun.env.POSTGRES_PASSWORD_FILE).text()
DB_PASSWORD = pw.trim()

As you can see in the example above, the location of the secret is typically still defined as an environment variable. For example, the official Postgres Docker image uses the env variable POSTGRES_PASSWORD_FILE to define the location of the password file. This is just a best practice and other official images follow this suggestion as well.

So in my current side project I use the same approach and pass the location of the secret file to both my application and the Postgres container at runtime.

Development

Since you are not typically running your application out of a Docker container in development, you need to make sure you pass secrets yourself. Either put a file in your project root, or provide the secret as an environment variable to be used preferentially if no secrets path is provided.

Trim Your Secrets

As you may have noticed in the example above, I trim the secret before using it. This actually cost me a lot of headaches because I kept getting authentication errors with the database while implementing this feature.

In my case, I was using Docker Compose to run my application and the Postgres database. I kept the password file right next to the docker-compose.yml and referenced it like this:

docker-compose.yml
services:
app:
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
secrets:
- db_password
db:
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password_file

Note that this docker-compose.yml is truncated for brevity.

There were no newlines or other invisible characters in the file. There are still none to this day… But I kept getting authentication errors. After a lot of debugging, console.loging, and just trying random things, I finally tried trimming the secret before using it.

And it worked! 🥳

My wife was sitting next to me on the couch when I finally got it to work, and she got pretty scared when I started screaming in excitement. Sorry, honey! 🫣

Conclusion

Docker secrets are a great way to pass sensitive information to your containers. You can use it with Docker Compose or Docker Swarm. Just make sure you trim the secret before using it. 😜