Как стать автором
Поиск
Написать публикацию
Обновить

Как подружить Bitwarden CLI с пайплайном деплоя

Уровень сложностиСредний
Время на прочтение6 мин
Количество просмотров377

Недавно я впервые занялся деплоем проектов и столкнулся с проблемой: как аккуратно подгружать переменные окружения для бэкенда и фронтенда.

На словах задача простая: подключи .env — и готово. Но на практике всё сложнее:

  • Вручную забивать ключи в админку (Coolify, Vercel, Heroku) неудобно. Каждый раз открывать интерфейс, копипастить значения, следить, чтобы они совпадали между окружениями… Короче, боль.

  • Права доступа обычно бинарные: либо у человека полный доступ ко всем переменным, либо никакого. Ограничить точечно нельзя.

  • Дублирование. В dev, staging и prod приходится руками синхронизировать одни и те же ключи. Ошибки неминуемы.

  • Нет истории изменений. Кто-то обновил переменную — и всё, старое значение потеряно.

  • Нет нормальной интеграции с пайплайном. Ключи можно скопировать в CI/CD, но это опять чужая админка и ручная работа.

Хочется чего-то системного:

  • все секреты хранятся в одном месте;

  • доступ можно раздавать точечно (одним — только dev, другим — только prod);

  • ключи автоматически подтягиваются при деплое.

Так я пришёл к Bitwarden Secrets Manager и уткнулся в детали, которых не нашёл ни в документации, ни на реддите. В этом посте делюсь готовым решением: как подружить Bitwarden CLI с деплоем через Docker.

Bitwarden Secrets Manager — это сервис, через который можно централизованно хранить, управлять и развертывать ключи в больших проектах.

Исходники

  • Бэкенд — NestJS.

  • Фронтенд — Vite.

  • Оба деплоятся через Docker, управляются через self-hosted Coolify.

Coolify — это open-source PaaS (аналог Heroku, Render, Railway). Запускает контейнеры, следит за логами, хранит ключи, делает деплой из гита. Маленькая облачная платформа на своём сервере.

Bitwarden Secrets Manager и CLI

У Bitwarden есть два полезных инструмента:

  • Secrets Manager — сервис для централизованного хранения ключей. Разные права доступа можно выдавать людям, сервисам или пайплайнам.

  • Bitwarden CLI (bws) — консольная утилита, которая умеет логиниться, доставать ключи из secrets manager и подставлять их куда угодно.

Комбо идеально подходит для CI/CD: все ключи хранятся в одном месте, а при деплое вы их автоматически подтягиваете.

Настраиваем Bitwarden

В Secrets Manager создаём два проекта:

  • dev — для переменных дев окружения,

  • prod — для переменных прода.

Чаще фронтенд и бэкенд разделяют на отдельные проекты. Здесь же для наглядности используем два: принцип работы не меняется, меняется уровень детализации.

Внутри каждого добавляем свои ключи:

  • prod.DATABASE_URL

  • prod.VITE_API_URL

  • dev.DATABASE_URL

  • dev.VITE_API_URL

В итоге получаем централизованное и понятное хранилище переменных.

Machine Account

Чтобы сервер мог сам подтягивать переменные, Bitwarden предлагает Machine Accounts. Это сервисные учётки для автоматизации.

  1. В Bitwarden (Secrets Manager) создаём Machine Account.

    • В админке — New → Machine account

    • Назначаем доступ: проектам dev и prod, права read (или read, write если нужно)

  2. Внутри аккаунта генерируем Access Token — он показывается один раз, сохраняем его.

С этого момента можно логиниться через CLI двумя способами:

  1. Один раз задать переменную окружения:
    export BWS_ACCESS_TOKEN="ваш_токен_доступа"
    bws secret list

  2. Или использовать токен при каждом запросе:
    bws secret list --access-token "ваш_токен_доступа"

Осталось научиться подгружать энвы из Bitwarden и подружить это с пайплайном наших приложений.

Автоматическая подгрузка ключей через sh скрипт

Вместо того, чтобы руками собирать .env, можно подружить bws с запуском контейнера. Для этого пишем sh-скрипт и делаем его точкой входа.

1. Скрипт для бэкенда

#!/bin/sh
set -e

# 1. Настраиваем сервер Bitwarden (кластер или self-host)
bws config server-base https://vault.bitwarden.eu

# 2. Определяем проект по NODE_ENV
if [ "$NODE_ENV" = "development" ]; then
  PROJECT_ID="$BW_PROJECT_DEV"
else
  PROJECT_ID="$BW_PROJECT_PROD"
fi

# 3. Загружаем ключи из Bitwarden Secrets Manager
SECRETS=$(bws secret list $PROJECT_ID --output json)

# 4. Сохраняем энвы в формате KEY=VALUE
TEMP_FILE=$(mktemp)
echo "$SECRETS" | jq -r '.[] | "\(.key)=\(.value)"' > "$TEMP_FILE"

# 5. Экспортируем в окружение контейнера
while IFS='=' read -r key value; do
  if [ -n "$key" ] && [ -n "$value" ]; then
    export "$key"="$value"
  fi
done < "$TEMP_FILE"

rm "$TEMP_FILE"

# 6. Запускаем приложение
exec node dist/main

Что тут происходит по шагам

  1. Критически важно указать сервер Bitwarden перед тем, как работать с bws: bws config server-base https://vault.bitwarden.eu

    • При регистрации в Bitwarden Cloud вы выбираете кластер, в котором будут храниться данные:

    • Если вы используете self-host версию, то указываете адрес вашего инстанса.

    Без этой настройки bws не будет знать, куда ходить за ключами. В документации этот момент упоминается вскользь, и легко на него наткнуться лбом (как это было у меня 🙂).

  2. Выбор проекта по окружению — логика простая: если NODE_ENV=development, берём BW_PROJECT_DEV, иначе — BW_PROJECT_PROD. Так одним образом деплоятся и dev, и prod

    Загрузка секретовbws secret list возвращает JSON с ключами и значениями.

  3. Парсим JSON — через jq приводим его к виду KEY=VALUE.

  4. Экспортируем переменные — скрипт обходит файл и выставляет всё в окружение текущего контейнера.

  5. Стартуем приложениеexec node dist/main заменяет shell-процесс на Node.js (чтобы Docker считал главным процессом контейнера именно приложение, а не оболочку).

2. Изменения в Dockerfile (бэкенд и фронт)

FROM node:22-slim

WORKDIR /app

# Устанавливаем утилиты, необходимые для bws
RUN apt-get update && apt-get install -y curl unzip jq && rm -rf /var/lib/apt/lists/*

# Устанавливаем Bitwarden SDK CLI (bws)
RUN curl -L "https://github.com/bitwarden/sdk-sm/releases/download/bws-v1.0.0/bws-x86_64-unknown-linux-gnu-1.0.0.zip" -o /tmp/bws.zip \
    && unzip /tmp/bws.zip -d /usr/local/bin \
    && chmod +x /usr/local/bin/bws \
    && rm /tmp/bws.zip

# Копируем скрипт entrypoint и делаем исполняемым
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

Так при старте контейнера первым делом выполнится скрипт, а после — запустится само приложение.

Так же пришлось заменить базовый образ на node:22-slim вместо node:22-alpine.
bws — это заранее скомпилированный бинарник под Linux glibc. Если попытаться запустить его на Alpine (musl), бинарник не найдёт нужные функции в системе и выдаст ошибку:

/usr/local/bin/bws: /lib/ld-musl-x86_64.so.1: bad ELF interpreter: No such file or directory

3. Интеграция с Coolify

Сначала получаем ID наших Bitwarden проектов:

  • Можно взять их из браузера открыв конкретный проект. В адресной строке будет что-то вроде:
    https://vault.bitwarden.eu/#/projects/abcd1234-5678-90ef-ghij-klmnopqrstuv
    Вот этот длинный UUID и есть ID проекта (abcd1234-5678-90ef-ghij-klmnopqrstuv).

  • Либо можно достать список проектов из bws: команда bws project list вернёт JSON со всеми проектами, где будут поля id и name. Например:

[
  {
    "id": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
    "name": "dev"
  },
  {
    "id": "wxyz9876-5432-10lk-jihg-fedcba098765",
    "name": "prod"
  }
]

В Configuration → Environment Variables для приложения добавляем:

BWS_ACCESS_TOKEN=ваш_access_token
BW_PROJECT_DEV=<id_dev_проекта>
BW_PROJECT_PROD=<id_prod_проекта>

Coolify пробросит их в контейнер, а entrypoint.sh подхватит и подтянет правильные переменные.

Нюансы интеграции с Vite

Для фронтенда на Vite Dockerfile остаётся таким же, как у бэкенда: используем node:22-slim, устанавливаем bws, копируем entrypoint.sh, ставим права и указываем его как ENTRYPOINT.

А вот скрипт отличается, потому что фронтенд — это статический билд, и переменные окружения нужно подставлять в HTML на этапе старта контейнера.

Для корректной подстановки переменных я добавил библиотеку vite-plugin-runtime-env — с ней process.env работает динамически даже в собранных Vite файлах.

Скрипт entrypoint.sh для Vite

#!/bin/sh
set -e

# 1. Настраиваем Bitwarden сервер
bws config server-base https://vault.bitwarden.eu

# 2. Выбираем проект по NODE_ENV
if [ "$NODE_ENV" = "development" ]; then
  PROJECT_ID="$BW_PROJECT_DEV"
else
  PROJECT_ID="$BW_PROJECT_PROD"
fi

# 3. Загружаем секреты из Bitwarden
SECRETS=$(bws secret list $PROJECT_ID --output json)

# 4. Экспортируем переменные окружения
TEMP_FILE=$(mktemp)
echo "$SECRETS" | jq -r '.[] | "\(.key)=\(.value)"' > "$TEMP_FILE"
while IFS='=' read -r key value; do
  if [ -n "$key" ] && [ -n "$value" ]; then
    export "$key"="$value"
  fi
done < "$TEMP_FILE"
rm "$TEMP_FILE"

# 5. Подставляем переменные в dist/index.html
if command -v npx >/dev/null 2>&1; then
  npx --yes envsub dist/index.html || echo "[WARN] envsub failed; proceeding with original index.html"
else
  echo "[WARN] npx not found; skipping env substitution"
fi

# 6. Запуск фронтенда
exec serve -s dist -l tcp://0.0.0.0:3000 -n

Для подстановки переменных в HTML здесь используется envsub (и поддержка vite-plugin-runtime-env), чтобы динамические значения попадали в index.html.

После того как скрипты настроены и Dockerfile обновлён, всё готово к запуску. Ручной ввод ключей в админке Coolify нужен всего один раз — чтобы задать три переменные: токен Machine Account и ID проектов для dev и prod.
При следующем деплое контейнер автоматически подтянет все ключи из Bitwarden, подставит их в окружение (и, для фронтенда, в HTML), а ручные ключи можно удалить.

Теперь можно деплоить как обычно: CI/CD пробрасывает токен Machine Account, entrypoint.sh подхватывает ключи, и приложение стартует с актуальными значениями.

Заключение

Мы рассмотрели, как подружить Bitwarden CLI с деплоем приложений на NestJS и Vite через Docker и Coolify, разобрали нюансы entrypoint‑скриптов, подстановку переменных и особенности фронтенда.

Буду рад, если этот гайд окажется полезным, и благодарен за обратную связь, вопросы или советы по улучшению процесса. 

Теги:
Хабы:
+3
Комментарии4

Публикации

Ближайшие события