mirror of https://github.com/docker/docs.git
Document Docker secrets
Fixes #529 Signed-off-by: Misty Stanley-Jones <misty@docker.com>
This commit is contained in:
parent
5b2842922c
commit
f4a28b31ec
|
@ -289,6 +289,8 @@ toc:
|
|||
title: Manage nodes in a swarm
|
||||
- path: /engine/swarm/services/
|
||||
title: Deploy services to a swarm
|
||||
- path: /engine/swarm/secrets/
|
||||
title: Manage sensitive data with Docker secrets
|
||||
- path: /engine/swarm/networking/
|
||||
title: Attach services to an overlay network
|
||||
- path: /engine/swarm/admin_guide/
|
||||
|
|
|
@ -0,0 +1,837 @@
|
|||
---
|
||||
title: Manage sensitive data with Docker secrets
|
||||
description: How to securely store, retrieve, and use sensitive data with Docker services
|
||||
keywords: swarm, secrets, credentials, sensitive strings, sensitive data, security, encryption, encryption at rest
|
||||
---
|
||||
|
||||
## About secrets
|
||||
|
||||
In terms of Docker Swarm services, a _secret_ is a blob of data, such as a
|
||||
password, SSH private key, SSL certificate, or another piece of data that should
|
||||
not be transmitted over a network or stored unencrypted in a Dockerfile or in
|
||||
your application's source code. In Docker 1.13 and higher, you can use Docker
|
||||
_secrets_ to centrally manage this data and securely transmit it to only those
|
||||
containers that need access to it. Secrets are encrypted during transit and at
|
||||
rest in a Docker swarm. A given secret is only accessible to those services
|
||||
which have been granted explicit access to it, and only while those service
|
||||
tasks are running.
|
||||
|
||||
You can use secrets to manage any sensitive data which a container needs at
|
||||
runtime but you don't want to store in the image or in source control, such as:
|
||||
|
||||
- Usernames and passwords
|
||||
- TLS certificates and keys
|
||||
- SSH keys
|
||||
- Other important data such as the name of a database or internal server
|
||||
- Generic strings or binary content (up to 500 kb in size)
|
||||
|
||||
> **Note**: Docker secrets are only available to swarm services, not to
|
||||
> standalone containers. To use this feature, consider adapting your container to
|
||||
> run as a service with a scale of 1.
|
||||
|
||||
Another use case for using secrets is to provide a layer of abstraction between
|
||||
the container and a set of credentials. Consider a scenario where you have
|
||||
separate development, test, and production environments for your application.
|
||||
Each of these environments can have different credentials, stored in the
|
||||
development, test, and production swarms with the same secret name. Your
|
||||
containers only need to know the name of the secret in order to function in all
|
||||
three environments.
|
||||
|
||||
## How Docker manages secrets
|
||||
|
||||
When you add a secret to the swarm, Docker sends the secret to the swarm manager
|
||||
over a mutual TLS connection. The secret is stored in the Raft log, which is
|
||||
encrypted. The entire Raft log is replicated across the other managers, ensuring
|
||||
the same high availability guarantees for secrets as for the rest of the swarm
|
||||
management data.
|
||||
|
||||
>**Warning**: Raft data is encrypted in Docker 1.13 and higher. If any of your
|
||||
Swarm managers run an earlier version, and one of those managers becomes the
|
||||
manager of the swarm, the secrets will be stored unencrypted in that node's Raft
|
||||
logs. Before adding any secrets, update all of your manager nodes to Docker 1.13
|
||||
to prevent secrets from being written to plain-text Raft logs.
|
||||
|
||||
When you grant a newly-created or running service access to a secret, the
|
||||
decrypted secret is mounted into the container in an in-memory filesystem at
|
||||
`/run/secrets/<secret_name>`. You can update a service to grant it access to
|
||||
additional secrets or revoke its access to a given secret at any time.
|
||||
|
||||
A node only has access to (encrypted) secrets if the node is a swarm manager or
|
||||
if it is running service tasks which have been granted access to the secret.
|
||||
When a container task stops running, the decrypted secrets shared to it are
|
||||
unmounted from the in-memory filesystem for that container and flushed from the
|
||||
node's memory.
|
||||
|
||||
If a node loses connectivity to the swarm while it is running a task container
|
||||
with access to a secret, the task container still has access to its secrets, but
|
||||
cannot receive updates until the node reconnects to the swarm.
|
||||
|
||||
You can add or inspect an individual secret at any time, or list all
|
||||
secrets. You cannot remove a secret that a running service is
|
||||
using. See [Rotate a secret](secrets.md#example-rotate-a-secret) for a way to
|
||||
remove a secret without disrupting running services.
|
||||
|
||||
In order to update or roll back secrets more easily, consider adding a version
|
||||
number or date to the secret name. This is made easier by the ability to control
|
||||
the mount point of the secret within a given container.
|
||||
|
||||
## Read more about `docker secret` commands
|
||||
|
||||
Use these links to read about specific commands, or continue to the
|
||||
[example about using secrets with a service](secrets.md#example-use-secrets-with-a-service).
|
||||
|
||||
- [`docker secret create`](../reference/commandline/secret_create.md)
|
||||
- [`docker secret inspect`](../reference/commandline/secret_inspect.md)
|
||||
- [`docker service ls`](../reference/commandline/secret_ls.md)
|
||||
- [`docker secret rm`](../reference/commandline/secret_rm.md)
|
||||
- [`--secret`](../reference/commandline/service_create.md#create-a-service-with-secrets) flag for `docker service create`
|
||||
- [`--secret-add` and `--secret-rm`](../reference/commandline/service_update.md#adding-and-removing-secrets) flags for `docker service update`
|
||||
|
||||
## Examples
|
||||
|
||||
This section includes three graduated examples which illustrate how to use
|
||||
Docker secrets. The images used in these examples have been updated to make it
|
||||
easier to use Docker secrets. To find out how to modify your own images in
|
||||
a similar way, see
|
||||
[Build support for Docker Secrets into your images](#build-support-for-docker-secrets-into-your-images).
|
||||
|
||||
> **Note**: These examples use a single-Engine swarm and unscaled services for
|
||||
> simplicity.
|
||||
|
||||
### Simple example: Get started with secrets
|
||||
|
||||
This simple example shows how secrets work in just a few commands. For a
|
||||
real-world example, continue to
|
||||
[Intermediate example: Use secrets with a Nginx service](#intermediate-example-use-secrets-with-a-nginx-service).
|
||||
|
||||
1. Add a secret to Docker. The `docker secret create` command reads standard
|
||||
input.
|
||||
|
||||
```bash
|
||||
$ echo "This is a secret" | docker secret create my_secret_data
|
||||
```
|
||||
|
||||
2. Create a `redis` service and grant it access to the secret. By default,
|
||||
the container can access the secret at `/run/secrets/<secret_name>`, but
|
||||
you can customize the file name on the container using the `target` option.
|
||||
|
||||
```bash
|
||||
$ docker service create --name="redis" --secret="my_secret_data" redis:alpine
|
||||
```
|
||||
|
||||
3. Verify that the task is running without issues using `docker service ps`. If
|
||||
everything is working, the output looks similar to this:
|
||||
|
||||
```bash
|
||||
$ docker service ps redis
|
||||
|
||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||
bkna6bpn8r1a redis.1 redis:alpine ip-172-31-46-109 Running Running 8 seconds ago
|
||||
```
|
||||
|
||||
If there were an error, and the task were failing and repeatedly restarting,
|
||||
you would see something like this:
|
||||
|
||||
```bash
|
||||
$ docker service ps redis
|
||||
|
||||
NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||
redis.1.siftice35gla redis:alpine moby Running Running 4 seconds ago
|
||||
\_ redis.1.whum5b7gu13e redis:alpine moby Shutdown Failed 20 seconds ago "task: non-zero exit (1)"
|
||||
\_ redis.1.2s6yorvd9zow redis:alpine moby Shutdown Failed 56 seconds ago "task: non-zero exit (1)"
|
||||
\_ redis.1.ulfzrcyaf6pg redis:alpine moby Shutdown Failed about a minute ago "task: non-zero exit (1)"
|
||||
\_ redis.1.wrny5v4xyps6 redis:alpine moby Shutdown Failed 2 minutes ago "task: non-zero exit (1)"
|
||||
```
|
||||
|
||||
4. Get the ID of the `redis` service task container using `docker ps` , so that
|
||||
you can use `docker exec` to connect to the container and read the contents
|
||||
of the secret data file, which defaults to being readable by all and has the
|
||||
same name as the name of the secret. The first command below illustrates
|
||||
how to find the container ID, and the second and third commands use shell
|
||||
completion to do this automatically.
|
||||
|
||||
```bash
|
||||
$ docker ps --filter name=redis -q
|
||||
|
||||
5cb1c2348a59
|
||||
|
||||
$ docker exec $(docker ps --filter name=redis -q) ls -l /run/secrets
|
||||
|
||||
total 4
|
||||
-r--r--r-- 1 root root 17 Dec 13 22:48 my_secret_data
|
||||
|
||||
$ docker exec $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data
|
||||
|
||||
This is a secret
|
||||
```
|
||||
|
||||
5. Verify that the secret is **not** available if you commit the container.
|
||||
|
||||
```bash
|
||||
$ docker commit $(docker ps --filter name=redis -q) committed_redis
|
||||
|
||||
$ docker run --rm -it committed_redis cat /run/secrets/my_secret_data
|
||||
|
||||
cat: can't open '/run/secrets/my_secret_data': No such file or directory
|
||||
```
|
||||
|
||||
6. Try removing the secret. The removal fails because the `redis` is running
|
||||
and has access to the secret.
|
||||
|
||||
```bash
|
||||
|
||||
$ docker secret ls
|
||||
|
||||
ID NAME CREATED UPDATED
|
||||
wwwrxza8sxy025bas86593fqs my_secret_data 4 hours ago 4 hours ago
|
||||
|
||||
|
||||
$ docker secret rm my_secret_data
|
||||
|
||||
Error response from daemon: rpc error: code = 3 desc = secret 'my_secret_data' is in use by the following service: redis
|
||||
```
|
||||
|
||||
7. Remove access to the secret from the running `redis` service by updating the
|
||||
service.
|
||||
|
||||
```bash
|
||||
$ docker service update --secret-rm="my_secret_data" redis
|
||||
```
|
||||
|
||||
8. Repeat steps 3 and 4 again, verifying that the service no longer has access
|
||||
to the secret. The container ID will be different, because the
|
||||
`service update` command redeploys the service.
|
||||
|
||||
```bash
|
||||
$ docker exec -it $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data
|
||||
|
||||
cat: can't open '/run/secrets/my_secret_data': No such file or directory
|
||||
```
|
||||
|
||||
7. Stop and remove the service, and remove the secret from Docker.
|
||||
|
||||
```bash
|
||||
$ docker service rm redis
|
||||
|
||||
$ docker secret rm my_secret_data
|
||||
```
|
||||
|
||||
### Intermediate example: Use secrets with a Nginx service
|
||||
|
||||
This example is divided into two parts.
|
||||
[The first part](#generate-the-site-certificate) is all about generating
|
||||
the site certificate and does not directly involve Docker secrets at all, but
|
||||
it sets up [the second part](#configure-the-nginx-container), where you store
|
||||
and use the site certificate and Nginx configuration as secrets.
|
||||
|
||||
#### Generate the site certificate
|
||||
|
||||
Generate a root CA and TLS certificate and key for your site. For production
|
||||
sites, you may want to use a service such as `Let’s Encrypt` to generate the
|
||||
TLS certificate and key, but this example uses command-line tools. This step
|
||||
is a little complicated, but is only a set-up step so that you have
|
||||
something to store as a Docker secret. If you want to skip these sub-steps,
|
||||
you can [use Let's Encrypt](https://letsencrypt.org/getting-started/) to
|
||||
generate the site key and certificate, name the files `site.key` and
|
||||
`site.crt`, and skip to
|
||||
[Configure the Nginx container](#configure-the-nginx-container).
|
||||
|
||||
1. Generate a root key.
|
||||
|
||||
```bash
|
||||
$ openssl genrsa -out "root-ca.key" 4096
|
||||
```
|
||||
|
||||
2. Generate a CSR using the root key.
|
||||
|
||||
```bash
|
||||
$ openssl req \
|
||||
-new -key "root-ca.key" \
|
||||
-out "root-ca.csr" -sha256 \
|
||||
-subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA'
|
||||
```
|
||||
|
||||
3. Configure the root CA. Edit a new file called `root-ca.cnf` and paste
|
||||
the following contents into it. This constrains the root CA to only be
|
||||
able to sign leaf certificates and not intermediate CAs.
|
||||
|
||||
```none
|
||||
[root_ca]
|
||||
basicConstraints = critical,CA:TRUE,pathlen:1
|
||||
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
|
||||
subjectKeyIdentifier=hash
|
||||
```
|
||||
|
||||
4. Sign the certificate.
|
||||
|
||||
```bash
|
||||
$ openssl x509 -req -days 3650 -in "root-ca.csr" \
|
||||
-signkey "root-ca.key" -sha256 -out "root-ca.crt" \
|
||||
-extfile "root-ca.cnf" -extensions \
|
||||
root_ca
|
||||
```
|
||||
|
||||
5. Generate the site key.
|
||||
|
||||
```bash
|
||||
$ openssl genrsa -out "site.key" 4096
|
||||
```
|
||||
|
||||
6. Generate the site certificate and sign it with the site key.
|
||||
|
||||
```bash
|
||||
$ openssl req -new -key "site.key" -out "site.csr" -sha256 \
|
||||
-subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost'
|
||||
```
|
||||
|
||||
7. Configure the site certificate. Edit a new file called `site.cnf` and
|
||||
paste the following contents into it. This constrains the site
|
||||
certificate so that it can only be used to authenticate a server and
|
||||
can't be used to sign certificates.
|
||||
|
||||
```none
|
||||
[server]
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
basicConstraints = critical,CA:FALSE
|
||||
extendedKeyUsage=serverAuth
|
||||
keyUsage = critical, digitalSignature, keyEncipherment
|
||||
subjectAltName = DNS:localhost, IP:127.0.0.1
|
||||
subjectKeyIdentifier=hash
|
||||
```
|
||||
|
||||
8. Sign the site certificate.
|
||||
|
||||
```bash
|
||||
$ openssl x509 -req -days 750 -in "site.csr" -sha256 \
|
||||
-CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \
|
||||
-out "site.crt" -extfile "site.cnf" -extensions server
|
||||
```
|
||||
|
||||
9. The `site.csr` and `site.cnf` files are not needed by the Nginx service, but
|
||||
you will need them if you want to generate a new site certificate. Protect
|
||||
the `root-ca.key` file.
|
||||
|
||||
#### Configure the Nginx container
|
||||
|
||||
1. Produce a very basic Nginx configuration that serves static files over HTTPS.
|
||||
The TLS certificate and key will be stored as Docker secrets so that they
|
||||
can be rotated easily.
|
||||
|
||||
In the current directory, create a new file called `site.conf` with the
|
||||
following contents:
|
||||
|
||||
```none
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name localhost;
|
||||
ssl_certificate /run/secrets/site.crt;
|
||||
ssl_certificate_key /run/secrets/site.key;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Create three secrets, representing the key, the certificate, and the
|
||||
`site.conf`. You can store any file as a secret as long as it is smaller
|
||||
than 500 KB. This allows you to decouple the key, certificate, and
|
||||
configuration from the services that will use them.
|
||||
|
||||
```bash
|
||||
$ cat site.key | docker secret create site.key
|
||||
|
||||
$ cat site.crt | docker secret create site.crt
|
||||
|
||||
$ cat site.conf | docker create site.conf
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker secret ls
|
||||
|
||||
ID NAME CREATED UPDATED
|
||||
2hvoi9mnnaof7olr3z5g3g7fp site.key 58 seconds ago 58 seconds ago
|
||||
aya1dh363719pkiuoldpter4b site.crt 24 seconds ago 24 seconds ago
|
||||
zoa5df26f7vpcoz42qf2csth8 site.conf 11 seconds ago 11 seconds ago
|
||||
```
|
||||
|
||||
4. Create a service that runs Nginx and has access to the three secrets. The
|
||||
last part of the `docker service create` command creates a symbolic link
|
||||
from the location of the `site.conf` secret to `/etc/nginx.conf.d/`, where
|
||||
Nginx looks for extra configuration files. This step happens before Nginx
|
||||
actually starts, so you don't need to rebuild your image if you change the
|
||||
Nginx configuration.
|
||||
|
||||
> **Note**: Normally you would create a Dockerfile which copies the `site.conf`
|
||||
> into place, build the image, and run a container using your custom image.
|
||||
> This example does not require a custom image. It puts the `site.conf`
|
||||
> into place and runs the container all in one step.
|
||||
|
||||
```bash
|
||||
$ docker service create \
|
||||
--name nginx \
|
||||
--secret site.key \
|
||||
--secret site.crt \
|
||||
--secret site.conf \
|
||||
--publish 3000:443 \
|
||||
nginx:latest \
|
||||
sh -c "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'"
|
||||
```
|
||||
|
||||
This uses the short syntax for the `--secret` flag, which creates files in
|
||||
`/run/secrets/` with the same name as the secret. Within the running
|
||||
containers, the following three files now exist:
|
||||
|
||||
- `/run/secrets/site.key`
|
||||
- `/run/secrets/site.crt`
|
||||
- `/run/secrets/site.conf`
|
||||
|
||||
5. Verify that the Nginx service is running.
|
||||
|
||||
```bash
|
||||
$ docker service ls
|
||||
|
||||
ID NAME MODE REPLICAS IMAGE
|
||||
zeskcec62q24 nginx replicated 1/1 nginx:latest
|
||||
|
||||
$ docker service ps nginx
|
||||
|
||||
NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||
nginx.1.9ls3yo9ugcls nginx:latest moby Running Running 3 minutes ago
|
||||
```
|
||||
|
||||
6. Verify that the service is operational: you can reach the Nginx
|
||||
server, and that the correct TLS certificate is being used.
|
||||
|
||||
```bash
|
||||
$ curl --cacert root-ca.crt https://0.0.0.0:3000
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to nginx!</title>
|
||||
<style>
|
||||
body {
|
||||
width: 35em;
|
||||
margin: 0 auto;
|
||||
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to nginx!</h1>
|
||||
<p>If you see this page, the nginx web server is successfully installed and
|
||||
working. Further configuration is required.</p>
|
||||
|
||||
<p>For online documentation and support please refer to
|
||||
<a href="http://nginx.org/">nginx.org</a>.<br/>
|
||||
Commercial support is available at
|
||||
<a href="http://nginx.com/">nginx.com</a>.</p>
|
||||
|
||||
<p><em>Thank you for using nginx.</em></p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```bash
|
||||
$ openssl s_client -connect 0.0.0.0:3000 -CAfile root-ca.crt
|
||||
|
||||
CONNECTED(00000003)
|
||||
depth=1 /C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
|
||||
verify return:1
|
||||
depth=0 /C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
|
||||
verify return:1
|
||||
---
|
||||
Certificate chain
|
||||
0 s:/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
|
||||
i:/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
|
||||
---
|
||||
Server certificate
|
||||
-----BEGIN CERTIFICATE-----
|
||||
…
|
||||
-----END CERTIFICATE-----
|
||||
subject=/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost
|
||||
issuer=/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA
|
||||
---
|
||||
No client certificate CA names sent
|
||||
---
|
||||
SSL handshake has read 1663 bytes and written 712 bytes
|
||||
---
|
||||
New, TLSv1/SSLv3, Cipher is AES256-SHA
|
||||
Server public key is 4096 bit
|
||||
Secure Renegotiation IS supported
|
||||
Compression: NONE
|
||||
Expansion: NONE
|
||||
SSL-Session:
|
||||
Protocol : TLSv1
|
||||
Cipher : AES256-SHA
|
||||
Session-ID: A1A8BF35549C5715648A12FD7B7E3D861539316B03440187D9DA6C2E48822853
|
||||
Session-ID-ctx:
|
||||
Master-Key: F39D1B12274BA16D3A906F390A61438221E381952E9E1E05D3DD784F0135FB81353DA38C6D5C021CB926E844DFC49FC4
|
||||
Key-Arg : None
|
||||
Start Time: 1481685096
|
||||
Timeout : 300 (sec)
|
||||
Verify return code: 0 (ok)
|
||||
```
|
||||
|
||||
7. To clean up after running this example, remove the `nginx` service and the
|
||||
stored secrets.
|
||||
|
||||
```bash
|
||||
$ docker service rm nginx
|
||||
|
||||
$ docker secret rm site.crt site.key nginx.conf
|
||||
```
|
||||
|
||||
### Advanced example: Use secrets with a WordPress service
|
||||
|
||||
In this example, you create a single-node MySQL service with a custom root
|
||||
password, add the credentials as secrets, and create a single-node WordPress
|
||||
service which uses these credentials to connect to MySQL. The
|
||||
[next example](#example-rotate-a-secret) builds on this one and shows you how to
|
||||
rotate the MySQL password and update the services so that the WordPress service
|
||||
can still connect to MySQL.
|
||||
|
||||
This example illustrates some techniques to use Docker secrets to avoid saving
|
||||
sensitive credentials within your image or passing them directly on the command
|
||||
line.
|
||||
|
||||
> **Note**: This example uses a single-Engine swarm for simplicity, and uses a
|
||||
> single-node MySQL service because a single MySQL server instance cannot be
|
||||
> scaled by simply using a replicated service, and setting up a MySQL cluster is
|
||||
> beyond the scope of this example.
|
||||
>
|
||||
> Also, changing a MySQL root passphrase isn’t as simple as changing
|
||||
> a file on disk. You must use a query or a `mysqladmin` command to change the
|
||||
> password in MySQL.
|
||||
|
||||
1. Generate a random alphanumeric password for MySQL and store it as a Docker
|
||||
secret with the name `mysql_password` using the `docker secret create`
|
||||
command. To make the password shorter or longer, adjust the last argument of
|
||||
the `openssl` command. This is just one way to create a relatively random
|
||||
password. You can use another command to generate the password if you
|
||||
choose.
|
||||
|
||||
> **Note**: After you create a secret, you cannot update it. You can only
|
||||
> remove and re-create it, and you cannot remove a secret that a service is
|
||||
> using. However, you can grant or revoke a running service's access to
|
||||
> secrets using `docker service update`. If you need the ability to update a
|
||||
> secret, consider adding a version component to the secret name, so that you
|
||||
> can later add a new version, update the service to use it, then remove the
|
||||
> old version.
|
||||
|
||||
```bash
|
||||
$ openssl rand -base64 20 | docker secret create mysql_password
|
||||
|
||||
l1vinzevzhj4goakjap5ya409
|
||||
```
|
||||
|
||||
The value returned is not the password, but the ID of the secret. In the
|
||||
remainder of this tutorial, the ID output is omitted.
|
||||
|
||||
Generate a second secret for the MySQL `root` user. This secret won't be
|
||||
shared with the WordPress service created later. It's only needed to
|
||||
bootstrap the `mysql` service.
|
||||
|
||||
```bash
|
||||
$ openssl rand -base64 20 | docker secret create mysql_root_password
|
||||
```
|
||||
|
||||
List the secrets managed by Docker using `docker secret ls`:
|
||||
|
||||
```bash
|
||||
$ docker secret ls
|
||||
|
||||
ID NAME CREATED UPDATED
|
||||
l1vinzevzhj4goakjap5ya409 mysql_password 41 seconds ago 41 seconds ago
|
||||
yvsczlx9votfw3l0nz5rlidig mysql_root_password 12 seconds ago 12 seconds ago
|
||||
```
|
||||
|
||||
The secrets are stored in the encrypted Raft logs for the swarm.
|
||||
|
||||
2. Create a user-defined overlay network which will be used for communication
|
||||
between the MySQL and WordPress services. There is no need to expose the
|
||||
MySQL service to any external host or container.
|
||||
|
||||
```bash
|
||||
$ docker network create -d overlay mysql_private
|
||||
```
|
||||
|
||||
3. Create the MySQL service. The MySQL service will have the following
|
||||
characteristics:
|
||||
|
||||
- Because the scale is set to `1`, only a single MySQL task runs.
|
||||
Load-balancing MySQL is left as an exercise to the reader and involves
|
||||
more than just scaling the service.
|
||||
- Only reachable by other containers on the `mysql_private` network.
|
||||
- Uses the volume `mydata` to store the MySQL data, so that it persists
|
||||
across restarts to the `mysql` service.
|
||||
- The secrets are each mounted in a `tmpfs` filesystem at
|
||||
`/run/secrets/mysql_password` and `/run/secrets/mysql_root_password`.
|
||||
They are never exposed as environment variables, nor can they be committed
|
||||
to an image if the `docker commit` command is run.
|
||||
- Sets the environment variables `MYSQL_PASSWORD_FILE` and
|
||||
`MYSQL_ROOT_PASSWORD_FILE` to point to the
|
||||
files `/run/secrets/mysql_password` and `/run/secrets/mysql_root_password`.
|
||||
The `mysql` image reads the password strings from those files when
|
||||
initializing the system database for the first time. Afterward, the
|
||||
passwords are stored in the MySQL system database itself.
|
||||
- Sets environment variables `MYSQL_USER` and `MYSQL_DATABASE`. A new
|
||||
database called `wordpress` is created when the container starts, and the
|
||||
`wordpress` user will have full permissions for this database only. This
|
||||
user will not be able to create or drop databases or change the MySQL
|
||||
configuration.
|
||||
|
||||
```bash
|
||||
$ docker service create \
|
||||
--name mysql \
|
||||
--replicas 1 \
|
||||
--network mysql_private \
|
||||
--mount type=volume,source=mydata,destination=/var/lib/mysql \
|
||||
--secret source=mysql_root_password,target=mysql_root_password \
|
||||
--secret source=mysql_password,target=mysql_password \
|
||||
-e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
|
||||
-e MYSQL_PASSWORD_FILE="/run/secrets/mysql_password" \
|
||||
-e MYSQL_USER="wordpress" \
|
||||
-e MYSQL_DATABASE="wordpress" \
|
||||
mysql:latest
|
||||
```
|
||||
|
||||
4. Verify that the `mysql` container is running using the `docker service ls` command.
|
||||
|
||||
```bash
|
||||
$ docker service ls
|
||||
|
||||
ID NAME MODE REPLICAS IMAGE
|
||||
wvnh0siktqr3 mysql replicated 1/1 mysql:latest
|
||||
```
|
||||
|
||||
At this point, you could actually revoke the `mysql` service's access to the
|
||||
`mysql_password` and `mysql_root_password` secrets because the passwords
|
||||
have been saved in the MySQL system database. Don't do that for now, because
|
||||
we will use them later to facilitate rotating the MySQL password.
|
||||
|
||||
5. Now that MySQL is set up, create a WordPress service that connects to the
|
||||
MySQL service. The WordPress service has the following characteristics:
|
||||
|
||||
- Because the scale is set to `1`, only a single WordPress task runs.
|
||||
Load-balancing WordPress is left as an exercise to the reader, because of
|
||||
limitations with storing WordPress session data on the container
|
||||
filesystem.
|
||||
- Exposes WordPress on port 30000 of the host machine, so that you can access
|
||||
it from external hosts. You can expose port 80 instead if you do not have
|
||||
a web server running on port 80 of the host machine.
|
||||
- Connects to the `mysql_private` network so it can communicate with the
|
||||
`mysql` container, and also publishes port 80 to port 30000 on all swarm
|
||||
nodes.
|
||||
- Has access to the `mysql_password` secret, but specifies a different
|
||||
target file name within the container. The WordPress container will use
|
||||
the mount point `/run/secrets/wp_db_password`. Also specifies that the
|
||||
secret is not group-or-world-readable, by setting the mode to
|
||||
`0400`.
|
||||
- Sets the environment variable `WORDPRESS_DB_PASSWORD_FILE` to the file
|
||||
path where the secret is mounted. The WordPress service will read the
|
||||
MySQL password string from that file and add it to the `wp-config.php`
|
||||
configuration file.
|
||||
- Connects to the MySQL container using the username `wordpress` and the
|
||||
password in `/run/secrets/wp_db_password` and creates the `wordpress`
|
||||
database if it does not yet exist.
|
||||
- Stores its data, such as themes and plugins, in a volume called `wpdata`
|
||||
so these files persist when the service restarts.
|
||||
|
||||
```bash
|
||||
$ docker service create \
|
||||
--name wordpress \
|
||||
--replicas 1 \
|
||||
--network mysql_private \
|
||||
--publish 30000:80 \
|
||||
--mount type=volume,source=wpdata,destination=/var/www/html \
|
||||
--secret source=mysql_password,target=wp_db_password,mode=0400 \
|
||||
-e WORDPRESS_DB_USER="wordpress" \
|
||||
-e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \
|
||||
-e WORDPRESS_DB_HOST="mysql:3306" \
|
||||
-e WORDPRESS_DB_NAME="wordpress" \
|
||||
wordpress:latest
|
||||
```
|
||||
|
||||
6. Verify the service is running using `docker service ls` and
|
||||
`docker service ps` commands.
|
||||
|
||||
```bash
|
||||
$ docker service ls
|
||||
|
||||
ID NAME MODE REPLICAS IMAGE
|
||||
wvnh0siktqr3 mysql replicated 1/1 mysql:latest
|
||||
nzt5xzae4n62 wordpress replicated 1/1 wordpress:latest
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker service ps wordpress
|
||||
|
||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||
aukx6hgs9gwc wordpress.1 wordpress:latest moby Running Running 52 seconds ago
|
||||
```
|
||||
|
||||
At this point, you could actually revoke the WordPress service's access to
|
||||
the `mysql_password` secret, because WordPress has copied the secret to its
|
||||
configuration file `wp-config.php`. Don't do that for now, because we will
|
||||
use it later to facilitate rotating the MySQL password.
|
||||
|
||||
7. Access `http://localhost:30000/` from any swarm node and set up WordPress
|
||||
using the web-based wizard. All of these settings are stored in the MySQL
|
||||
`wordpress` database. WordPress automatically generates a password for your
|
||||
WordPress user, which is completely different from the password WordPress
|
||||
uses to access MySQL. Store this password securely, such as in a password
|
||||
manager. You will need it to log into WordPress after
|
||||
[rotating the secret](#example-rotate-a-secret).
|
||||
|
||||
Go ahead and write a blog post or two and install a WordPress plugin or
|
||||
theme to verify that WordPress is fully operational and its state is saved
|
||||
across service restarts.
|
||||
|
||||
8. Do not clean up any services or secrets if you intend to proceed to the next
|
||||
example, which demonstrates how to rotate the MySQL root password.
|
||||
|
||||
|
||||
### Example: Rotate a secret
|
||||
|
||||
This example builds upon the previous one. In this scenario, you create a new
|
||||
secret with a new MySQL password, update the `mysql` and `wordpress` services to
|
||||
use it, then remove the old secret.
|
||||
|
||||
**Note**: Changing the password on a MySQL database involves running extra
|
||||
queries or commands, as opposed to just changing a single environment variable
|
||||
or a file, since the image only sets the MySQL password if the database doesn’t
|
||||
already exist, and MySQL stores the password within a MySQL database by default.
|
||||
Rotating passwords or other secrets will often involve additional steps outside
|
||||
of Docker.
|
||||
|
||||
1. Create the new password and store it as a secret named `mysql_password_v2`.
|
||||
|
||||
```bash
|
||||
$ openssl rand -base64 20 | docker secret create mysql_password_v2
|
||||
```
|
||||
|
||||
2. Update the MySQL service to give it access to both the old and new secrets.
|
||||
Remember that you cannot update or rename a secret, but you can revoke a
|
||||
secret and grant access to it using a new target filename.
|
||||
|
||||
```bash
|
||||
$ docker service update \
|
||||
--secret-rm mysql_password mysql
|
||||
|
||||
$ docker service update \
|
||||
--secret-add source=mysql_password,target=old_mysql_password \
|
||||
--secret-add source=mysql_password_v2,target=mysql_password \
|
||||
mysql
|
||||
```
|
||||
|
||||
Updating a service causes it to restart, and when the MySQL service restarts
|
||||
the second time, it has access to the old secret under
|
||||
`/run/secrets/old_mysql_password` and the new secret under
|
||||
`/run/secrets/mysql_password`.
|
||||
|
||||
Even though the MySQL service has access to both the old and new secrets
|
||||
now, the MySQL root password has not yet been changed.
|
||||
|
||||
3. Now, change the MySQL password for the `wordpress` user using the `mysql`
|
||||
CLI. This command reads the old and new password from the files in
|
||||
`/run/secrets` but does not expose them on the command line or save them in
|
||||
the shell history.
|
||||
|
||||
Do this quickly and move on to the next step, because WordPress will lose
|
||||
the ability to connect to MySQL.
|
||||
|
||||
First, find the ID of the `mysql` container task.
|
||||
|
||||
```bash
|
||||
$ docker ps --filter --name=mysql -q
|
||||
|
||||
c7705cf6176f
|
||||
```
|
||||
|
||||
Substitute the ID in the command below, or use the second variant which
|
||||
uses shell expansion to do it all in a single step.
|
||||
|
||||
```bash
|
||||
$ docker exec <CONTAINER_ID> \
|
||||
bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"'
|
||||
```
|
||||
|
||||
**or**:
|
||||
|
||||
```bash
|
||||
$ docker exec $(docker ps --filter --name=mysql -q) \
|
||||
bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"'
|
||||
```
|
||||
|
||||
4. Update the `wordpress` service to use the new password, keeping the target
|
||||
path at `/run/secrets/wp_db_secret` and keeping the file permissions at
|
||||
`0400`. This will trigger a rolling restart of the WordPress service and
|
||||
the new secret will be used.
|
||||
|
||||
```bash
|
||||
$ docker service update \
|
||||
--secret-rm mysql_password \
|
||||
--secret-add source=mysql_password_v2,target=wp_db_password,mode=0400 \
|
||||
wordpress
|
||||
```
|
||||
|
||||
5. Verify that WordPress works by browsing to http://localhost:30000/ on any
|
||||
swarm node again. You'll need to use the WordPress username and password
|
||||
from when you ran through the WordPress wizard in the previous task.
|
||||
|
||||
Verify that the blog post you wrote still exists, and if you changed any
|
||||
configuration values, verify that they are still changed.
|
||||
|
||||
6. Revoke access to the old secret from the MySQL service and
|
||||
remove the old secret from Docker.
|
||||
|
||||
```bash
|
||||
|
||||
$ docker service update \
|
||||
--secret-rm mysql_password \
|
||||
mysql
|
||||
|
||||
$ docker secret rm mysql_password
|
||||
```
|
||||
|
||||
|
||||
7. If you want to try the running all of these examples again or just want to
|
||||
clean up after running through them, use these commands to remove the
|
||||
WordPress service, the MySQL container, the `mydata` and `wpdata` volumes,
|
||||
and the Docker secrets.
|
||||
|
||||
```bash
|
||||
$ docker service rm wordpress mysql
|
||||
|
||||
$ docker volume rm mydata wpdata
|
||||
|
||||
$ docker secret rm mysql_password_v2 mysql_root_password
|
||||
```
|
||||
|
||||
## Build support for Docker Secrets into your images
|
||||
|
||||
If you develop a container that can be deployed as a service and requires
|
||||
sensitive data, such as a credential, as an environment variable, consider
|
||||
adapting your image to take advantage of Docker secrets. One way to do this is
|
||||
to ensure that each parameter you pass to the image when creating the container
|
||||
can also be read from a file.
|
||||
|
||||
Many of the official images in the
|
||||
[Docker library](https://github.com/docker-library/), such as the
|
||||
[wordpress](https://github.com/docker-library/wordpress/)
|
||||
image used in the above examples, have been updated in this way.
|
||||
|
||||
When you start a WordPress container, you provide it with the parameters it
|
||||
needs by setting them as environment variables. The WordPress image has been
|
||||
updated so that the environment variables which contain important data for
|
||||
WordPress, such as `WORDPRESS_DB_PASSWORD`, also have variants which can read
|
||||
their values from a file (`WORDPRESS_DB_PASSWORD_FILE`). This strategy ensures
|
||||
that backward compatibility is preserved, while allowing your container to read
|
||||
the information from a Docker-managed secret instead of being passed directly.
|
||||
|
||||
>**Note**: Docker secrets do not set environment variables directly. This was a
|
||||
conscious decision, because environment variables can unintentionally be leaked
|
||||
between containers (for instance, if you use `--link`).
|
|
@ -66,7 +66,7 @@ $ docker service create --name helloworld alpine ping docker.com
|
|||
9uk4639qpg7npwf3fn2aasksr
|
||||
```
|
||||
|
||||
## Configuring services
|
||||
## Configure services
|
||||
|
||||
When you create a service, you can specify many different configuration options
|
||||
and constraints. See the output of `docker service create --help` for a full
|
||||
|
@ -99,6 +99,12 @@ $ docker service create --name helloworld \
|
|||
9uk4639qpg7npwf3fn2aasksr
|
||||
```
|
||||
|
||||
### Grant a service access to secrets
|
||||
|
||||
To create a service with access to Docker-managed secrets, use the `--secret`
|
||||
flag. For more information, see
|
||||
[Manage sensitive strings (secrets) for Docker services](secrets.md)
|
||||
|
||||
### Specify the image version the service should use
|
||||
|
||||
When you create a service without specifying any details about the version of
|
||||
|
|
Loading…
Reference in New Issue