What are Docker Secrets?
Docker secrets are sensitive data, values or configurations used by a containerized application or system that should not be visible and available only to those containers that need access to it.
This post provides a comprehensive guide to using Docker secrets, defining them and the types of data that are considered secrets, how to set up secrets using Docker CLI and Docker Compose and security best practices for it.
Definition of sensitive information
Sensitive information is a piece of data that presents a high risk of exposure and financial loss to the organization if an unauthorized party gains access to it. Secrets are considered sensitive information, meaning they are not meant to be shared publicly; instead, they are meant to be hard to access and view in plain form.
You can denote any piece of information as sensitive depending on the context. For example, in the context of Docker containers, application secrets, database credentials, and SSH keys are more sensitive than leaked employee emails. If an attacker can access your systems due to unsafe practices, they can do much more direct damage.
On the other hand, employee emails can be easily inferred or guessed, and they’re not necessarily meant to be hidden. Sure, they can be used for phishing attempts and other social engineering attacks, but their exposure does not create an immediate risk.
So, when using Docker secrets, you first need to categorize the pieces of information that are sensitive according to context. You must also follow best practices for handling them securely. We will describe the different types of secrets that you need to be aware of below.
Types of Docker Secrets
Secrets are credentials used to provide access to privileged accounts, applications, and services. They are essentially the “keys to the kingdom,” and handing them to complete strangers will get you into trouble. Here are some representative examples of secrets:
Passwords
A password is a single string of text or a token that is known only to you and a third-party provider. Passwords and access tokens are examples of secrets. You wouldn’t want them exposed to unknown parties since they could use them to log into your backend, impersonate your account, and then use this leverage to perform malicious attacks.
SSH Keys
An SSH key (and more specifically, the private key part) is considered a secret. The SSH protocol is an access control system, and its primary purpose is to authenticate users and systems. You can use the SSH protocol to log in remotely from one computer to another in a secure manner.
As a public key can be restored from a private key (but not the other way around), exposing a private key means that an attacker can use it to log into your system.
SSL(TLS) Certificates
SSL is a communication protocol that establishes secure communication between clients and servers. When you’re visiting a website, for example, it keeps internet connections secure when you’re using login forms or interacting with online banking so that no middleman can see the contents of your account.
SSL(TLS) certificates can also be used for mTLS communication between application services and can prevent various kinds of attacks.
Other Sensitive Data
Other important pieces of sensitive information can include:
- Database credentials: Usernames and database names can be used to connect to databases. If attackers somehow gain access to a password value, they still have to guess the rest of the credentials to gain access.
- Hard-coded credentials: Sometimes an application has a hidden backdoor coded with specific credentials that allow access into the system. Although they’re hard-coded, these credentials still pose a risk if they’re exposed to the public.
Manage Secrets with Docker CLI
Let’s spin up our local Docker daemon and show you how to manage Docker secrets using the CLI.
The docker secrets –help
command lists the available options we have for managing secrets:
$ docker secret --help
Usage: docker secret COMMAND
Manage Docker secrets
Commands:
create Create a secret from a file or STDIN as content
inspect Display detailed information on one or more secrets
ls List secrets
rm Remove one or more secrets
Code language: JavaScript (javascript)
Let’s go through them one by one.
Create
To create a new secret, you’ll use the create
command. You need to provide a file or consume the contents of the secret from the command line:
$ cat secret.json
{
"username": "theo",
"password": "password"
}%
Code language: JavaScript (javascript)
For technical and security reasons, you cannot create secrets if your dockerd
service is not in swarm mode. If it isn’t, you’ll get a message like this:
$ docker secret create my_credentials secret.json
Error response from daemon: This node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again.
Code language: JavaScript (javascript)
Running docker swarm init
will enable swarm mode so that you can create secrets using the CLI:
$ docker info |grep Swarm
Swarm: inactive
$ docker swarm init
Swarm initialized: current node (dfqc8qfvy8992lmmx737d0p4e) is now a manager.
$ docker info |grep Swarm
Swarm: active
$ docker secret create my_credentials secret.json
szv7anyechly226td0gmdtvxk
The string you get after running this command is a unique ID field for reference.
Here is an example of the STDIN use case:
$ echo -n "password" | docker secret create a_password -Aixt00c7zvhr1b1n9w673lz22
Code language: PHP (php)
docker secret ls
You can inspect the list of secrets using the docker secret ls
command:
$ docker secret ls
ID NAME DRIVER CREATED UPDATED
aixt00c7zvhr1b1n9w673lz22 a_password 3 minutes ago 3 minutes ago
szv7anyechly226td0gmdtvxk my_credentials 5 minutes ago 5 minutes ago
This shows the ID field that we mentioned earlier, the secret’s name, and the time elapsed since it was created and last updated. The DRIVER is the name of the provider used to fetch the secret’s value from an external secret store (which could be Vault, for example).
docker secret inspect
You can inspect a specific secret by using the docker secret inspect
command. You will need to provide either the ID or the name:
$ docker secret inspect my_credentials
[
{
"ID": "szv7anyechly226td0gmdtvxk",
"Version": {
"Index": 11
},
"CreatedAt": "2023-02-06T14:32:41.4086062Z",
"UpdatedAt": "2023-02-06T14:32:41.4086062Z",
"Spec": {
"Name": "my_credentials",
"Labels": {}
}
}
]
Code language: JavaScript (javascript)
Of course, you don’t really get to see the actual values of the secret payload here. You just get to see the metadata.
docker secret rm
Finally, you can delete a secret using the docker secret rm
command passing the ID/name of the secret:
$ docker secret rm a_password
a_password
You cannot delete a secret if it is used by a service or container. To see this in practice, you can create the following service, consume the secret, and then try to delete it:
$ docker service create --name memcached --secret my_credentials memcached:alpine
$ docker exec `docker ps -f name=memcached -q` ls -l /run/secrets
total 4
-r--r--r-- 1 root root 48 Feb 7 10:54 my_credentials
$ docker secret rm my_credentials
Error response from daemon: rpc error: code = InvalidArgument desc = secret 'my_credentials' is in use by the following service: memcached
Code language: JavaScript (javascript)
To unmount a secret from a service, you can use the docker service update
command with the secret-rm
parameter:
$ docker service update --secret-rm my_credentials memcached
Memcached
$ docker exec `docker ps -f name=memcached -q` ls -l /run/secrets
ls: /run/secrets: No such file or directory
$ docker secret rm my_credentials
My_credentials
Code language: JavaScript (javascript)
You can add the secret back after you’ve created another one using the docker service update
command with the secret-add
parameter:
$ docker service update --secret-add my_credentials memcached
Note that the actual contents of the secret are not encrypted, but they’re passed (as when you create a secret):
$ docker exec `docker ps -f name=memcached -q` cat /run/secrets/my_credentials
{
"username": "theo",
"password": "password"
}
Code language: JavaScript (javascript)
Manage Secrets with Docker Compose
You can also specify secrets using Docker compose. When you define the docker-compose.yml
file, you need to add specific fields that will be used to create and mount the secrets in the container services. For example, take a look at the following Redis service:
version: "3.9"
services:
redis:
image: redis:latest
container_name: redis
command: [
"bash", "-c",
'
docker-entrypoint.sh
--requirepass "$(cat $REDIS_PASS_FILE)"
'
]
volumes:
- .:/var/lib/redis/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
environment:
REDIS_PASS_FILE: /run/secrets/redis_password
secrets:
- redis_password
ports:
- "6379"
secrets:
redis_password:
file: redis_password.txt
Code language: PHP (php)
There is a global secrets
field that specifies the source of the secrets. In this case, the secret is located in the local file called redis_password.txt.
It uses the name redis_password
as the secret name in the Redis service definition.
The last part is actually to make sure that the Redis server reads that value from /run/secrets/redis_password.
Because the current image does not allow reading that from a file, we need to use an env
variable and the cat
command to pipe it into the docker-entrypoint.sh
.
Some images allow us to read the password without having to use a custom command. For example, the official mysql
image as well as the MYSQL_ROOT_PASSWORD_FILE
and MYSQL_PASSWORD_FILE
env variables can be used to point to the mounted secrets files.
Note that you don’t have to use docker secret create
beforehand, as docker-compose will manage that for you.
How Does Docker Store Secrets?
To get a bit more technical, the way that Docker handles secrets for containers is as follows:
The Docker CLI interacts with the dockerd
service using HTTP for managing secrets. For example, this code deals with sending a docker secret create
command to the daemon.
The dockerd
service handles the list of docker secret
commands and interacts with the container backend service.
In the container backend, when a container is created, it avoids storing credentials and secrets within environmental variables; instead, it provides the files /run/secrets/<secret_name>
( or C:\ProgramData\Docker\secrets<secret_name>
on Windows) under the mount in a tempfs
filesystem.
This section of the code is responsible for mounting the secrets in the container. This is called during the createSpec function, which is the base call when creating a container based on an image. Finally, the definitions of secret types are located here.
Docker Security Best Practices
To ensure the security of your Docker containers, you need to follow certain security practices when handling secrets. Follow these Dockerfile security best practices and recommendations for safeguarding the sensitive information stored within these environments:
Avoid Passing Secrets as Environment Variables
Passing secrets via environment (env) variables is convenient, but unsafe. Using the docker inspect
command, anyone with the right access to the node can inspect the values in plain form:
$ docker run --env PASSWORD=mypassword --name memcached -d memcached:alpine
555741257afe8e1e03b9728c1a49bba9883daad9b4795c818a06f6534b7fc347
$ docker exec `docker ps -f name=memcached -q` env | grep PASSWORD
PASSWORD=mypassword
Code language: JavaScript (javascript)
Use a Secrets Management Service
Although Docker mounts secrets in containers in a safe way, the actual storage of the secrets needs to be managed by the IT team. Things like secret rotation, access control, and encryption are delegated to a dedicated secrets service like Vault. This could work as part of an automation script, for example.
A secrets management service will securely store all the secrets that your organization needs to operate. When building containers and services via CI/CD pipelines, the secrets are fetched and passed on as inline variables to the Docker service or docker-compose.
If a secret needs to be rotated, then the workflow will go through all containers and update them with that secret, triggering an application reload. This enables the safe storage and retrieval of secrets in application containers.
Avoid Putting Secrets in Dockerfiles
You should never put secrets in a Dockerfile. For obvious reasons, this includes hard-coding secrets into version control (which is unsafe by default).
Conclusion
This concludes our exploration of Docker secrets. We need secrets to keep code and configurations separate as well as to ensure that application credentials are not exposed to the public.
In this guide, we explained what Docker secrets are and why they are important for security and compliance. We also showed you how to manage secrets using Docker CLI and Docker Compose. Finally, we described several Docker security best practices for managing secrets.
When it comes to Docker secrets, detecting threats and suspicious behavior in a scalable way requires a more sophisticated tool.
With Falco, you can create a “Runtime Security” layer that will actively detect and prevent secrets from escaping encrypted status, either by monitoring env variables or certain system calls or by committing them to version control.