Back to Blog
General

testcontainers and Github Actions Matrix Strategies to dynamically run tests against HashiCorp Vaults last 3 major versions

June 25, 2025·3 min read
GithubGithub ActionOpen SourceGolangtestcontainers
testcontainers and Github Actions Matrix Strategies to dynamically run tests against HashiCorp Vaults last 3 major versions

TL;DR testcontainers

testcontainers is a powerful testing library available in multiple languages. It allows you to spin up (and tear down) containers during your unit or end-to-end tests with ease. I've been using it for years in my open source projects to mock APIs and dependencies like Vault, localstack, or Redis. testcontainers includes a wide range of supported modules ready for use in your tests.

Running Tests Against the Last 3 Major Versions of HashiCorp Vault

Recently, I wanted to use testcontainers to test vkv — a Go CLI tool for importing and exporting KV secrets — against the last three major versions of HashiCorp Vault. Needless to say, I wanted to avoid manually updating the versions every time a new Vault version was released.

💡 I got it working with a little curl and shell magic

Step 1: Fetch the Last 3 Major Versions of HashiCorp Vault

We start with a GitHub Actions job to fetch Vault’s latest tags from its GitHub repo. We then filter the tags to keep only those that end in .0, extract the top three, and write them to GITHUB_OUTPUT:

[...]
vault-versions:
    runs-on: ubuntu-latest
    outputs:
      vault-versions: ${{ steps.vault-versions.outputs.versions }}

    steps:
      - name: Fetch latest tags
        id: vault-versions
        run: | 
          echo "versions=$(curl -s "https://api.github.com/repos/hashicorp/vault/tags?per_page=100" \
          | jq -r '.[].name' \
          | grep -E '^v?[0-9]+\.[0-9]+\.0$' \
          | sed 's/^v//' \
          | head -n 3 \
          | jq -cRn '[inputs]')" >> "$GITHUB_OUTPUT"

      - name: Last 3 Vault major versions
        run: echo "${{ steps.vault-versions.outputs.versions }}"

Step 2: Run Tests Using a Matrix Strategy

We then use the output from the previous step to run a test job against each version using GitHub’s matrix strategy. The current version is passed to the test via the VAULT_VERSION environment variable:

test:
    runs-on: ubuntu-latest
    needs: vault-versions
    strategy:
      matrix:
        version: ${{ fromJson(needs.vault-versions.outputs.vault-versions) }}
    steps:
      - uses: actions/checkout@v4

      - run: go generate -tags tools tools/tools.go

      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
          cache: false

      - name: go get
        run: go get ./...

      - name: Run coverage
        run: |
          gotestsum -- -v -race -coverprofile="coverage.out" -covermode=atomic ./...
        env:
          VAULT_VERSION: ${{ matrix.version }}

Step 3: Use the Version in Go Code

In the Go test code, Im checking for the presence of the VAULT_VERSION environment variable. If it’s set, we use its value; otherwise, we fall back to a default version:

var VaultVersion ="1.20.0"

func StartTestContainer(commands ...string) (*TestContainer, error) {
	ctx := context.Background()

	if v, ok := os.LookupEnv(VaultVersionEnv); ok {
		VaultVersion = v
	}

	vaultContainer, err := vault.Run(ctx, "hashicorp/vault:" + VaultVersion,
		vault.WithToken(token),
		vault.WithInitCommand(commands...),
	)
	if err != nil {
		return nil, fmt.Errorf("failed to start container: %w", err)
	}

	uri, err := vaultContainer.HttpHostAddress(ctx)
	if err != nil {
		return nil, fmt.Errorf("error returning container mapped port: %w", err)
	}

	return &TestContainer{
		Container: vaultContainer,
		URI:       uri,
		Token:     token,
	}, nil
}

Result

As you can see here, this creates a separate test job for each of the last three major versions of HashiCorp Vault!

Now every time a new major Vault version is released, your test matrix stays up to date — without any manual updates.