Skip to content
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
761fa3a
Initial pass at building a DB seeding pipeline
mimartin12 Apr 17, 2026
09f21b8
Add -o --output parameters and a new JSON arg, which provides machine…
mimartin12 Apr 17, 2026
9afd95c
Merge branch 'add-output-flag-db-seeder-util' into feat/seeder-build-…
mimartin12 Apr 17, 2026
e5ea38d
Merge remote-tracking branch 'origin/main' into feat/seeder-build-pip…
mimartin12 Apr 17, 2026
a88d36a
build seeded script, wrap with GitHub actions
mimartin12 May 6, 2026
c7640ee
Merge branch 'main' into feat/seeder-build-pipeline
mimartin12 May 6, 2026
5eb8d25
Fix broken merge from main
mimartin12 May 6, 2026
d178789
Add .net restore
mimartin12 May 6, 2026
dc0e611
Fix connection string
mimartin12 May 6, 2026
4ab6e4c
Leverage cache and extend build to MSSQL and MySQL
mimartin12 May 6, 2026
4d74615
Fix postgres insert to use public.
mimartin12 May 6, 2026
b8a1ad4
Clean up metadata in database, rely on docker metadata entirely
mimartin12 May 6, 2026
c9cbff4
Merge branch 'main' into feat/seeder-build-pipeline
mimartin12 Jun 5, 2026
6202b7f
Add cron and a curated list
mimartin12 Jun 8, 2026
ad79e8d
Merge branch 'main' into feat/seeder-build-pipeline
mimartin12 Aug 6, 2026
bd808c7
Merge remote-tracking branch 'origin/main' into feat/seeder-build-pip…
mimartin12 Aug 6, 2026
2bb0d0b
Derive preset category from name, drop presets-metadata.json
mimartin12 Aug 6, 2026
aa929b3
Poll for the published container port instead of reading it once
mimartin12 Aug 6, 2026
459cf92
Ship a core bundle with the data protection key and attachment blobs
mimartin12 Aug 6, 2026
687c7b7
Merge remote-tracking branch 'origin/main' into feat/seeder-build-pip…
mimartin12 Aug 6, 2026
5d3aafa
Seed as self-hosted so licensing works without the machine cert store
mimartin12 Aug 7, 2026
261022c
Pull the data protection key from Key Vault
mimartin12 Aug 7, 2026
d3a7536
Require a data protection key and document consuming a CI build
mimartin12 Aug 7, 2026
466bc08
Add the scale presets to the default build list
mimartin12 Aug 10, 2026
a12d900
Document running BW Lite against a seeded image
mimartin12 Aug 10, 2026
1e3578a
Document running self-host against a seeded image
mimartin12 Aug 10, 2026
f129486
Fail the mssql seed image when the attach fails
mimartin12 Aug 11, 2026
9bcbb35
Document pulling and loading a seeded image for self-host
mimartin12 Aug 11, 2026
60681c8
Summarize built images in one table
mimartin12 Aug 11, 2026
fc25fdd
Gate the seeded mssql image on an attached seed
mimartin12 Aug 11, 2026
94ca593
Tag seeded images with -latest
mimartin12 Aug 11, 2026
3a72b11
List only built images in the summary table
mimartin12 Aug 11, 2026
8742f44
Retire the bare preset tag
mimartin12 Aug 11, 2026
b2953f2
Bump workflow actions to the versions main pins
mimartin12 Aug 11, 2026
6279753
Match the mssql base image to util/MsSql
mimartin12 Aug 11, 2026
f514272
Note that sqlite is local only
mimartin12 Aug 11, 2026
e0cc83e
Forward SIGTERM to sqlservr in the mssql entrypoint
mimartin12 Aug 11, 2026
53d3103
Remove published bundle, keep attachments
mimartin12 Aug 11, 2026
ee261fb
Drop the feature-branch push trigger
mimartin12 Aug 11, 2026
be6dcbc
Key the concurrency group on the dispatch inputs
mimartin12 Aug 11, 2026
e2f030f
Merge branch 'main' into feat/seeder-build-pipeline
mimartin12 Aug 11, 2026
d3c2e3e
Assert on row count in the seeded-database check
mimartin12 Aug 11, 2026
f49055b
Add -b to the mssql detach so a failed offline fails the build
mimartin12 Aug 11, 2026
d3f2a31
Merge branch 'main' into feat/seeder-build-pipeline
mimartin12 Aug 11, 2026
7a97807
Build seeded images on push to main
mimartin12 Aug 12, 2026
6a6c19b
Drop the `push` trigger
mimartin12 Aug 13, 2026
ddb3e2a
Added a quick callout to the presets.md file referencing _DEFAULT_PRE…
mimartin12 Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions .github/workflows/build-seeded-databases.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
name: Build Seeded Database Images

on:
schedule:
- cron: "0 2 * * 0"
workflow_dispatch:
inputs:
preset:
description: "Preset name to build (empty = curated default list, all = every preset)"
required: false
type: string
database:
description: "Database type to build (all = default matrix)"
required: false
default: all
type: choice
options:
- all
- postgres
- mysql
- mariadb
- mssql

env:
_AZ_REGISTRY: bitwardenprod.azurecr.io
_DEFAULT_PRESETS: >-
["qa.dunder-mifflin-enterprise-full",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 Given these hardcoded strings you may want to add a comment near their definition that this should also be updated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good callout, I added something in presets.md.
ddb3e2a

"scale.md-balanced-sterling-cooper",
"scale.lg-balanced-wayne-enterprises",
"scale.lg-highperm-tyrell-corp",
"scale.xl-broad-initech"]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Comment thread
mimartin12 marked this conversation as resolved.

jobs:
setup:
name: Determine build matrix
runs-on: ubuntu-22.04
outputs:
presets: ${{ steps.matrix.outputs.presets }}
databases: ${{ steps.matrix.outputs.databases }}
steps:
- name: Check out repo
if: inputs.preset == 'all'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up .NET
if: inputs.preset == 'all'
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
cache: true
cache-dependency-path: "**/*.csproj"

- name: Build SeederUtility
if: inputs.preset == 'all'
run: dotnet build util/SeederUtility/SeederUtility.csproj

- name: Determine matrix
id: matrix
env:
INPUT_PRESET: ${{ inputs.preset }}
INPUT_DATABASE: ${{ inputs.database }}
run: |
if [[ "${INPUT_PRESET}" == "all" ]]; then
presets=$(dotnet run --project util/SeederUtility --no-build -- preset --list --output json \
| jq -c '[.organization[], .individual[]]')
elif [[ -n "${INPUT_PRESET}" ]]; then
presets="[\"${INPUT_PRESET}\"]"
else
presets="${_DEFAULT_PRESETS}"
fi

if [[ -n "${INPUT_DATABASE}" && "${INPUT_DATABASE}" != "all" ]]; then
databases="[\"${INPUT_DATABASE}\"]"
else
databases='["postgres","mysql","mariadb","mssql"]'
fi

echo "presets=${presets}" >> "$GITHUB_OUTPUT"
echo "databases=${databases}" >> "$GITHUB_OUTPUT"
echo "Preset matrix: ${presets}"
echo "Database matrix: ${databases}"

build:
name: ${{ matrix.database }} / ${{ matrix.preset }}
needs: setup
runs-on: ubuntu-22.04
permissions:
contents: read
id-token: write
strategy:
matrix:
preset: ${{ fromJson(needs.setup.outputs.presets) }}
database: ${{ fromJson(needs.setup.outputs.databases) }}
fail-fast: false

steps:
- name: Check out repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
cache: true
cache-dependency-path: "**/*.csproj"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Set up QEMU
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0

- name: Restore .NET local tools
run: dotnet tool restore

- name: Log in to Azure
uses: bitwarden/gh-actions/azure-login@main
with:
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
client_id: ${{ secrets.AZURE_CLIENT_ID }}

- name: Retrieve data protection key
id: retrieve-secret
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: gh-org-bitwarden
secrets: "DP-KEY-XML"

- name: Log out from Azure
uses: bitwarden/gh-actions/azure-logout@main

- name: Build seeded image
env:
PUSH: "false"
REGISTRY: ${{ env._AZ_REGISTRY }}
GIT_SHA: ${{ github.sha }}
MATRIX_PRESET: ${{ matrix.preset }}
MATRIX_DATABASE: ${{ matrix.database }}
DP_KEY_XML: ${{ steps.retrieve-secret.outputs.DP-KEY-XML }}
run: |
GIT_SHA="${GIT_SHA:0:7}" \
bash util/SeederUtility/scripts/build-seeded-image.sh \
"${MATRIX_PRESET}" \
"${MATRIX_DATABASE}"

- name: Save image as tarball
env:
MATRIX_PRESET: ${{ matrix.preset }}
MATRIX_DATABASE: ${{ matrix.database }}
run: |
TAG="${MATRIX_PRESET//./-}"
DB="${MATRIX_DATABASE}"
SHA_SHORT="${GITHUB_SHA:0:7}"
docker save \
"${_AZ_REGISTRY}/shot/seeded-${DB}:${TAG}-latest" \
"${_AZ_REGISTRY}/shot/seeded-${DB}:${TAG}-${SHA_SHORT}" \
-o "seeded-${DB}-${TAG}-${SHA_SHORT}.tar"

- name: Upload image artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: seeded-${{ matrix.database }}-${{ matrix.preset }}
path: seeded-*.tar
retention-days: 7
if-no-files-found: error

- name: Stage attachment blobs
run: |
mkdir -p bundle-out
for f in util/SeederUtility/docker/bundles/seeded-core-*.tar.gz; do
tar -xzf "${f}" -C bundle-out
done

- name: Upload attachment blobs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: seeded-attachments-${{ matrix.database }}-${{ matrix.preset }}
path: bundle-out/core/attachments/**
retention-days: 7
if-no-files-found: ignore

summary:
name: Image summary
needs: [setup, build]
if: always()
runs-on: ubuntu-22.04
permissions:
contents: read
actions: read
steps:
- name: Render image table
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRESETS: ${{ needs.setup.outputs.presets }}
DATABASES: ${{ needs.setup.outputs.databases }}
GIT_SHA: ${{ github.sha }}
run: |
SHA_SHORT="${GIT_SHA:0:7}"
results=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs" \
--paginate --jq '.jobs[] | [.name, .conclusion] | @tsv')

{
echo "### Seeded images for \`${SHA_SHORT}\`"
echo ""
echo "| Preset | Database | Image |"
echo "| --- | --- | --- |"
while read -r db; do
while read -r preset; do
tag="${preset//./-}"
conclusion=$(printf '%s\n' "${results}" \
| awk -F'\t' -v name="${db} / ${preset}" '$1 == name { print $2; exit }')
if [ "${conclusion}" != "success" ]; then
continue
fi
echo "| ${preset} | ${db} | \`${_AZ_REGISTRY}/shot/seeded-${db}:${tag}-${SHA_SHORT}\` |"
done < <(echo "${PRESETS}" | jq -r '.[]')
done < <(echo "${DATABASES}" | jq -r '.[]')
echo ""
echo "Images are built, not pushed. Download them from this run's artifacts."
} >> "$GITHUB_STEP_SUMMARY"
9 changes: 9 additions & 0 deletions util/SeederUtility/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
docker/dp-keys/
docker/licenses/
docker/bundles/
docker/seed.sql
docker/**/seed.sql
docker/**/seed.bak
docker/**/*.mdf
docker/**/*.ldf
docker/*/build/
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Bit.Core.Entities;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.Seeder.Pipeline;
using Bit.Seeder.Services;
using Bit.SharedWeb.Utilities;
Expand Down Expand Up @@ -34,7 +35,11 @@ public static void ConfigureServices(ServiceCollection services, bool enableMang
services.AddSingleton<IPasswordHasher<User>, PasswordHasher<User>>();
services.TryAddSingleton<ISeedReader, SeedReader>();

services.AddDataProtection().SetApplicationName("Bitwarden");
var dpBuilder = services.AddDataProtection().SetApplicationName("Bitwarden");
if (CoreHelpers.SettingHasValue(globalSettings.DataProtection.Directory))
{
dpBuilder.PersistKeysToFileSystem(new DirectoryInfo(globalSettings.DataProtection.Directory));
}

services.AddAttachmentStorageService(globalSettings);

Expand Down
16 changes: 16 additions & 0 deletions util/SeederUtility/docker/mariadb/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM mariadb:12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Are all of these sufficiently covered by Renovate? Should you maybe add a grouping entry so this upgrades with the other(s)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered by Renovate, yes, by default since dockerfile is in the enabled managers.

Would the grouping happen automatically by Renovate? I would assume MariaDB, for example, would just get bumped in future PRs like this one. #7119


ARG PRESET_NAME=unknown
ARG PRESET_CATEGORY=unknown
ARG GIT_SHA=unknown
ARG BUILD_DATE=unknown

LABEL bitwarden.seeder.preset="${PRESET_NAME}"
LABEL bitwarden.seeder.category="${PRESET_CATEGORY}"
LABEL org.opencontainers.image.revision="${GIT_SHA}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"

ENV MARIADB_DATABASE=vault_dev
ENV MARIADB_ROOT_PASSWORD=Password1!

COPY seed.sql /docker-entrypoint-initdb.d/seed.sql
29 changes: 29 additions & 0 deletions util/SeederUtility/docker/mssql/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM mcr.microsoft.com/mssql/server:2025-CU5-ubuntu-24.04

ARG PRESET_NAME=unknown
ARG PRESET_CATEGORY=unknown
ARG GIT_SHA=unknown
ARG BUILD_DATE=unknown

LABEL bitwarden.seeder.preset="${PRESET_NAME}"
LABEL bitwarden.seeder.category="${PRESET_CATEGORY}"
LABEL org.opencontainers.image.revision="${GIT_SHA}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"

ENV ACCEPT_EULA=Y
ENV MSSQL_PID=Developer

USER root
RUN mkdir -p /seed
COPY vault_dev.mdf /seed/vault_dev.mdf
COPY vault_dev_log.ldf /seed/vault_dev_log.ldf
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

# Healthy only once the seed is attached, so dependents can wait on it rather than
# racing the attach and migrating an empty database into place
HEALTHCHECK --interval=10s --timeout=15s --retries=90 --start-period=30s \
CMD /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P "${SA_PASSWORD}" -C \
-d vault -b -Q "SET NOCOUNT ON; SELECT TOP 1 1 FROM [User]" > /dev/null 2>&1

ENTRYPOINT ["/docker-entrypoint.sh"]
Loading
Loading