
Хранить пароли базы данных, API-ключи и токены в .env-файлах на сервере - плохая практика. При компрометации сервера или случайном попадании файла в git все секреты утекают сразу. HashiCorp Vault решает эту задачу: секреты хранятся зашифрованными, доступ управляется через политики, каждое чтение секрета логируется. Разберём установку через Docker, инициализацию и интеграцию с приложениями.
Почему Vault, а не просто зашифрованный файл
Зашифрованный файл требует ключа для расшифровки - и этот ключ где-то нужно хранить. Vault решает проблему иначе:
Секреты зашифрованы ключами, которые никогда не хранятся на диске в открытом виде
Доступ к секретам управляется через токены и политики
Каждый доступ к секрету записывается в audit log
Поддерживается ротация секретов без перезапуска приложений
Секреты могут генерироваться динамически (временные credentials для БД)
Vault 1.16 - актуальная версия на момент написания статьи.
Установка через Docker
На VPS с Ubuntu 24.04 самый простой способ развернуть Vault - Docker.
# Установка Docker, если ещё нет
curl -fsSL https://get.docker.com | bash
# Создаём директории
mkdir -p /opt/vault/{config,data,logs}
Конфиг Vault /opt/vault/config/vault.hcl:
storage "file" {
path = "/vault/data"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = "true"
}
api_addr = "http://127.0.0.1:8200"
cluster_addr = "http://127.0.0.1:8201"
ui = true
Запуск через Docker:
docker run -d \
--name vault \
--cap-add=IPC_LOCK \
-p 127.0.0.1:8200:8200 \
-v /opt/vault/config:/vault/config \
-v /opt/vault/data:/vault/data \
-v /opt/vault/logs:/vault/logs \
--restart unless-stopped \
hashicorp/vault:1.16 vault server -config=/vault/config/vault.hcl
docker ps | grep vault
Флаг --cap-add=IPC_LOCK нужен, чтобы Vault мог блокировать память и предотвращать запись секретов в swap.

Инициализация Vault
При первом запуске Vault находится в состоянии "sealed" (запечатан) - его нужно инициализировать.
# Устанавливаем переменную окружения
export VAULT_ADDR='http://127.0.0.1:8200'
# Инициализация: создаём 5 ключей, нужно 3 для unseal
docker exec vault vault operator init -key-shares=5 -key-threshold=3
Вывод будет выглядеть примерно так:
Unseal Key 1: abc123...
Unseal Key 2: def456...
Unseal Key 3: ghi789...
Unseal Key 4: jkl012...
Unseal Key 5: mno345...
Initial Root Token: hvs.CAESIJK...
Сохраните unseal-ключи и root token в надёжном месте. Без 3 из 5 ключей данные восстановить невозможно. В реальных проектах ключи распределяют по разным людям или сохраняют в разных хранилищах.
Unseal - разблокировка
После каждого рестарта Vault запечатан. Для разблокировки вводим 3 из 5 ключей:
docker exec -it vault vault operator unseal abc123...
docker exec -it vault vault operator unseal def456...
docker exec -it vault vault operator unseal ghi789...
# Проверяем статус
docker exec vault vault statusСтатус после успешного unseal:
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 1.16.0
HA Enabled false
Автоматический unseal через переменные окружения (для разработки - не для продакшена!):
# В docker-compose.yml
environment:
- VAULT_DEV_ROOT_TOKEN_ID=myroot
- VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200
В dev-режиме (vault server -dev) unseal не нужен, но данные хранятся только в памяти.
Работа с секретами KV
Авторизация
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='hvs.CAESIJK...'
# Или через docker exec
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=hvs.CAESIJK... vault vault statusВключение KV secrets engine
# KV v2 поддерживает версионирование секретов
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault secrets enable -path=secret kv-v2Запись секретов
# Записываем секреты для приложения
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault kv put secret/myapp/database \
host="localhost" \
port="5432" \
name="myapp_db" \
username="dbuser" \
password="SuperSecretPass123"
# Записываем API-ключи
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault kv put secret/myapp/api \
stripe_key="sk_live_..." \
sendgrid_key="SG...."Чтение секретов
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault kv get secret/myapp/database
# Только конкретное поле
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault kv get -field=password secret/myapp/database
# JSON-вывод
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault kv get -format=json secret/myapp/database | jq .data.data

Политики доступа
Root token даёт полный доступ - для приложений создают ограниченные политики.
Создаём файл политики /tmp/myapp-policy.hcl:
# Читать секреты конкретного приложения
path "secret/data/myapp/*" {
capabilities = ["read"]
}
# Просматривать список (не значения)
path "secret/metadata/myapp/*" {
capabilities = ["list"]
}
Применяем политику:
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault policy write myapp-read /tmp/myapp-policy.hcl
Создаём токен с этой политикой:
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=$ROOT_TOKEN \
vault vault token create -policy=myapp-read -ttl=720h
# Вывод:
# token hvs.CAESIHab...
# token_ttl 720h
Этот токен передаётся приложению. Он может только читать секреты из secret/data/myapp/ - ничего больше.
Интеграция с приложениями
Через переменные окружения (bash-скрипт)
Создаём /opt/myapp/load-secrets.sh:
#!/bin/bash
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='hvs.CAESIHab...'
# Получаем секреты и экспортируем
DB_CREDS=$(vault kv get -format=json secret/myapp/database | jq -r '.data.data')
export DB_HOST=$(echo $DB_CREDS | jq -r '.host')
export DB_PASSWORD=$(echo $DB_CREDS | jq -r '.password')
export DB_NAME=$(echo $DB_CREDS | jq -r '.name')
# Запускаем приложение с переменными
exec "$@"
Использование:
chmod +x /opt/myapp/load-secrets.sh
/opt/myapp/load-secrets.sh node /opt/myapp/server.jsЧерез HTTP API
Любое приложение может получить секреты через REST API:
curl -s \
-H "X-Vault-Token: hvs.CAESIHab..." \
http://127.0.0.1:8200/v1/secret/data/myapp/database | jq .data.dataPython-пример
import hvac
import os
client = hvac.Client(
url='http://127.0.0.1:8200',
token=os.environ['VAULT_TOKEN']
)
secret = client.secrets.kv.v2.read_secret_version(
path='myapp/database',
mount_point='secret'
)
db_password = secret['data']['data']['password']
db_host = secret['data']['data']['host']Node.js-пример
const fetch = require('node-fetch');
async function getSecret(path) {
const response = await fetch(`http://127.0.0.1:8200/v1/secret/data/${path}`, {
headers: { 'X-Vault-Token': process.env.VAULT_TOKEN }
});
const data = await response.json();
return data.data.data;
}
const dbCreds = await getSecret('myapp/database');Версионирование секретов KV v2
KV v2 хранит историю изменений:
# Получить версию 2
vault kv get -version=2 secret/myapp/database
# Список версий
vault kv metadata get secret/myapp/database
# Откатить к версии 1
vault kv rollback -version=1 secret/myapp/databaseАвтоматический unseal через systemd
Для автоматического запуска после перезагрузки без ручного unseal используют Transit auto-unseal или AWS KMS. Простой вариант для одиночного сервера - скрипт с ключами (не самый безопасный, но практичный):
cat > /opt/vault/unseal.sh << 'EOF'
#!/bin/bash
export VAULT_ADDR='http://127.0.0.1:8200'
sleep 5 # ждём запуска контейнера
vault operator unseal KEY1
vault operator unseal KEY2
vault operator unseal KEY3
EOF
chmod 600 /opt/vault/unseal.shЗащитите этот файл - только root должен иметь к нему доступ.
Мониторинг и аудит
Включение audit log:
vault audit enable file file_path=/vault/logs/audit.log
Просмотр кто что читал:
cat /opt/vault/logs/audit.log | jq 'select(.type=="response") | {time: .time, path: .request.path, client: .request.remote_address}'

Итог
Vault решает задачу управления секретами через три механизма: хранилище с шифрованием, политики доступа по токенам и полный audit trail. Для небольшого VPS достаточно KV v2 через Docker с file-хранилищем. Главное - сразу создавать отдельные токены для каждого приложения с минимально необходимыми правами, а root token использовать только для администрирования. Регулярно ротируйте application-токены командой vault token renew или создавайте новые.