What is Hashicorp Vault?
Vault is software the provides secure secret management to protect sensitive data and in this article we will be demonstrating how to use vault docker to create and manage a secrets engine. Secrets may be anything from text properties or data to tokens, passwords, X509 certificates, and both symmetric and asymmetric keys used for encryption, authentication, and signing. Vault is API driven and can be used with standard REST API client software or their built in CLI tools, or even the Vault UI.
The primary purpose of this article is to cover example use of vault in a docker environment. With just a bit of configuration and Docker knowledge, Hashicorp Vault can be can be up and running with docker-compose in a few minutes.
Running a local instance of vault with docker and docker-compose
The official vault docker image is available in Docker Hub. The latest version can be pulled as demonstrated below in the docker-compose.yml file with vault:latest.
To keep things together and hopefully simple, create a new directory on your system and navigate to it.
mkdir docker-vault; cd docker-vault
vault docker compose
In the newly created directory, create and then open a docker-compose.yml file.
version: "3.8" services: vault-server: image: vault:latest ports: - "8200:8200" environment: VAULT_ADDR: "http://0.0.0.0:8200" VAULT_DEV_ROOT_TOKEN_ID: "vault-plaintext-root-token" cap_add: - IPC_LOCK networks: vault-network: ipv4_address: 172.21.0.10 aliases: - vault-server vault-client: build: . environment: VAULT_ADDR: "http://vault-server:8200" networks: vault-network: ipv4_address: 172.21.0.20 aliases: - vault-client networks: vault-network: ipam: config: - subnet: 172.21.0.0/24
To breakdown this docker-compose.yml file, we have two containers being composed in addition to a local network.
- Uses the latest vault image
- exposes port 8200
- Sets the VAULT_ADDR environment variable as recommended at server startup and sets the VAULT_DEV_ROOT_TOKEN_ID environment variable in order to initialize the root token to be used by the vault-client container.
- Sets the cap_add IPC_LOCK to allow vault to lock memory.
- Add vault-server to the local network.
- Builds from a Dockerfile in the same directory. This will be detailed below.
- Sets the VAULT_ADDR environment variable to talk to the vault server.
- Add vault-client to the local network.
- Create a local network for the vault-client and vault-server to run in.
vault client Dockerfile
Next, create the Dockerfile mentioned above to build the vault-client. Make sure this is in the same directory as the docker-compose.yml file.
FROM ubuntu:20.04 RUN apt-get update && apt-get install -y software-properties-common curl gnupg2 && \ curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - && \ apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" && \ apt-get update && apt-get install -y \ vault && \ setcap cap_ipc_lock= /usr/bin/vault COPY run.sh ./ CMD ./run.sh
The Dockerfile above will create a container from the ubuntu 20.04 image and install the necessary dependencies to run as a vault client. The Dockerfile then instructs a COPY and CMD of run.sh, detailed below.
#!/bin/bash VAULT_RETRIES=5 echo "Vault is starting..." until vault status > /dev/null 2>&1 || [ "$VAULT_RETRIES" -eq 0 ]; do echo "Waiting for vault to start...: $((VAULT_RETRIES--))" sleep 1 done echo "Authenticating to vault..." vault login token=vault-plaintext-root-token echo "Initializing vault..." vault secrets enable -version=2 -path=my.secrets kv echo "Adding entries..." vault kv put my.secrets/dev username=test_user vault kv put my.secrets/dev password=test_password echo "Complete..."
The run.sh is the script being ran as the vault-client.
- The client waits on the server to start by way of the vault status command. In this example, 5 seconds should be plenty. For your use case adjust as necessary.
- Before managing secrets it’s necessary to first authenticate to vault. In this example, we’re authenticating with the root token set as an environment variable to the vault-server container in the docker-compose.yml file. Note that this is used in a dev environment for simplicity but is not meant to run in a production or sensitive environment.
- Initialize a vault secrets engine.
- Add secrets to the secrets engine created in the previous step.
In the same directory as the three files created above, run the docker-compose script.
Because neither the vault-client nor the vault-server depend on one another for startup, they will both start at the same time. However, the vault-client will not attempt to setup the vault secrets engine until the vault-server is ready because of the wait functionality in the run.sh script. After the vault-server is initialized, you will see output similar to:
vault-server_1 | WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory vault-server_1 | and starts unsealed with a single unseal key. The root token is already vault-server_1 | authenticated to the CLI, so you can immediately begin using Vault. vault-server_1 | vault-server_1 | You may need to set the following environment variable: vault-server_1 | vault-server_1 | $ export VAULT_ADDR='http://0.0.0.0:8200' vault-server_1 | vault-server_1 | The unseal key and root token are displayed below in case you want to vault-server_1 | seal/unseal the Vault or re-authenticate. vault-server_1 | vault-server_1 | Unseal Key: a6aBk5g4VY90GQeoZR9b9DmLw3VYfDZSt+rvBSK5eA0= vault-server_1 | Root Token: vault-plaintext-root-token vault-server_1 | vault-server_1 | Development mode should NOT be used in production installations! vault-server_1 |
After the vault-server is initialized you will see the vault client begin to authenticate and create the secrets engine and subsequent secrets. You will see something similar to the following in your console output.
vault-client_1 | Authenticating to vault... vault-client_1 | Success! You are now authenticated. The token information displayed below vault-client_1 | is already stored in the token helper. You do NOT need to run "vault login" vault-client_1 | again. Future Vault requests will automatically use this token. vault-client_1 | vault-client_1 | Key Value vault-client_1 | --- ----- vault-client_1 | token vault-plaintext-root-token vault-client_1 | token_accessor OwRbae1JS0h5BpRiI7x8zC2L vault-client_1 | token_duration ∞ vault-client_1 | token_renewable false vault-client_1 | token_policies ["root"] vault-client_1 | identity_policies  vault-client_1 | policies ["root"] vault-client_1 | Initializing vault... vault-client_1 | Success! Enabled the kv secrets engine at: my.secrets/ vault-client_1 | Adding entries... vault-client_1 | Key Value vault-client_1 | --- ----- vault-client_1 | created_time 2021-03-24T01:32:00.912828188Z vault-client_1 | deletion_time n/a vault-client_1 | destroyed false vault-client_1 | version 1 vault-client_1 | Key Value vault-client_1 | --- ----- vault-client_1 | created_time 2021-03-24T01:32:00.944778246Z vault-client_1 | deletion_time n/a vault-client_1 | destroyed false vault-client_1 | version 2 vault-client_1 | Complete... docker-vault_vault-client_1 exited with code 0
Notice the exit code 0 of the vault-client. This indicates success, and will shutdown the vault-client. Any exit code other than 0 will indicate an error.
Let us know in the comments if you would like to see more examples of what you can do with vault in a docker environment. While we have attempted to provide a Hashicorp vault tutorial and examples, let us know what examples will provide additional value.
If you are interested in reading more of our content check out our Blog page.