feat(cli): add Docker image with alpine and debian variants (#8001)

This commit is contained in:
Sundram
2026-05-14 18:45:17 +05:30
committed by GitHub
parent 612b99460b
commit c282a955f8
6 changed files with 528 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
# Bruno CLI Docker Images
Official Docker images for [Bruno CLI](https://www.usebruno.com), enabling container-native API collection runs in CI/CD pipelines and local environments without requiring Node.js or npm on the host.
## Image structure
```text
docker/
├── README.md ← you are here
└── images/
├── alpine/
│ ├── Dockerfile ← Alpine Linux variant (smallest, ~141MB)
│ └── README.md
└── debian/
├── Dockerfile ← Debian slim variant (~200MB+, glibc support)
└── README.md
```
---
## Registries
```bash
docker pull usebruno/cli:latest
docker pull ghcr.io/usebruno/cli:latest
```
---
## Variants
| Variant | Base image | Details |
|---------|-----------|---------|
| **Alpine** (default) | `node:22-alpine` | [→ Alpine README](./images/alpine/README.md) |
| **Debian** | `node:22-slim` | [→ Debian README](./images/debian/README.md) |
### Quick choice
- **Use Alpine** unless you have a specific reason not to (90% of users)
- **Use Debian** if you hit SSL/glibc compatibility issues
---
## Tags
| Tag | Example | Variant |
|-----|---------|---------|
| `latest` | `usebruno/cli:latest` | alpine |
| `<version>` | `usebruno/cli:3.3.0` | alpine |
| `<major.minor>` | `usebruno/cli:3.3` | alpine |
| `<major>` | `usebruno/cli:3` | alpine |
| `<version>-alpine` | `usebruno/cli:3.3.0-alpine` | alpine |
| `<version>-debian` | `usebruno/cli:3.3.0-debian` | debian |
| `debian` | `usebruno/cli:debian` | debian |
---
## Step-by-step guide
### Step 1 — Pull the image
```bash
# latest (alpine by default — smallest, fastest to pull)
docker pull usebruno/cli:latest
# specific version (recommended for production CI)
docker pull usebruno/cli:3.3.0
# major.minor — gets patch updates automatically
docker pull usebruno/cli:3.3
# debian variant
docker pull usebruno/cli:debian
docker pull usebruno/cli:3.3.0-debian
```
---
### Step 2 — Check it works
```bash
docker run --rm usebruno/cli --version
```
---
### Step 3 — Run your collection
> Mount your collection directory to `/bruno` and pass `bru` arguments directly after the image name.
> **Cross-platform note:** the examples below use `$(pwd)` which works in Bash / Zsh / Git Bash / WSL.
> On Windows native shells, substitute `$(pwd)` with:
> - PowerShell: `${PWD}`
> - CMD: `%cd%`
```bash
# collection at your current directory
docker run --rm -v $(pwd):/bruno usebruno/cli run --env staging
# collection in a subfolder
docker run --rm -v $(pwd):/bruno usebruno/cli run ./api-tests --env staging
# single request file
docker run --rm -v $(pwd):/bruno usebruno/cli run ./api-tests/login.bru --env staging
```
---
### Step 4 — Choose your environment
```bash
docker run --rm -v $(pwd):/bruno usebruno/cli run --env local
docker run --rm -v $(pwd):/bruno usebruno/cli run --env staging
docker run --rm -v $(pwd):/bruno usebruno/cli run --env production
```
---
### Step 5 — Pass variables at runtime
```bash
# override a single variable
docker run --rm \
-v $(pwd):/bruno \
usebruno/cli run --env staging --env-var API_KEY=your_key
# override multiple variables
docker run --rm \
-v $(pwd):/bruno \
usebruno/cli run --env staging \
--env-var BASE_URL=https://api.example.com \
--env-var API_KEY=secret123
# load variables from a file
docker run --rm \
-v $(pwd):/bruno \
--env-file .env \
usebruno/cli run --env staging
```
---
### Step 6 — Save test results
```bash
# JSON report
docker run --rm \
-v $(pwd):/bruno \
usebruno/cli run --env staging --output results.json --format json
# JUnit XML report (for CI test reporters)
docker run --rm \
-v $(pwd):/bruno \
usebruno/cli run --env staging --output results.xml --format junit
```
---
### Step 7 — Stop on first failure
```bash
docker run --rm -v $(pwd):/bruno usebruno/cli run --env staging --bail
```
---
### Step 8 — Pin the right version
```bash
# exact version — safest for production, no surprise updates
docker run --rm -v $(pwd):/bruno usebruno/cli:3.3.0 run --env staging
# major.minor — gets patch fixes automatically
docker run --rm -v $(pwd):/bruno usebruno/cli:3.3 run --env staging
# latest — always newest, not recommended for production CI
docker run --rm -v $(pwd):/bruno usebruno/cli:latest run --env staging
```
---
### Step 9 — Choose alpine or debian
```bash
# alpine (default) — use this for most cases
docker run --rm -v $(pwd):/bruno usebruno/cli:3.3.0 run --env staging
# debian — use if you hit SSL, glibc, or native module issues
docker run --rm -v $(pwd):/bruno usebruno/cli:3.3.0-debian run --env staging
```
---
## Usage by variant
### Alpine variant
See [Alpine README](./images/alpine/README.md) for:
- Building the Alpine image
- When to use Alpine
- Variant-specific options
### Debian variant
See [Debian README](./images/debian/README.md) for:
- Building the Debian image
- When to use Debian
- Compatibility notes
---
## CI/CD integration
### GitHub Actions
```yaml
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Bruno collection
run: |
docker run --rm \
-v ${{ github.workspace }}:/bruno \
usebruno/cli:3.3 run --env staging --output results.xml --format junit
- name: Publish Test Report
uses: dorny/test-reporter@v3
if: always()
with:
name: Bruno Test Results
path: results.xml
reporter: java-junit
```
### GitLab CI
```yaml
api-tests:
image: usebruno/cli:3.3
script:
- bru run --env staging --output results.xml --format junit
artifacts:
reports:
junit: results.xml
```
---
## Image details
All variants include:
- **Entrypoint:** `bru`
- **Working directory:** `/bruno`
- **User:** `node` (UID 1000, non-root)
- **Architectures:** `linux/amd64`, `linux/arm64`
---
## All version × variant combinations
| | Alpine | Debian |
|---|---|---|
| `latest` | `usebruno/cli:latest` | `usebruno/cli:debian` |
| `3` | `usebruno/cli:3` | `usebruno/cli:3-debian` |
| `3.3` | `usebruno/cli:3.3` | `usebruno/cli:3.3-debian` |
| `3.3.0` | `usebruno/cli:3.3.0` | `usebruno/cli:3.3.0-debian` |
| `3.2.0` | `usebruno/cli:3.2.0` | `usebruno/cli:3.2.0-debian` |

View File

@@ -0,0 +1,34 @@
FROM node:22-alpine
LABEL maintainer="Bruno <engineering@usebruno.com>"
ARG BRUNO_VERSION
ENV BRUNO_VERSION=${BRUNO_VERSION}
LABEL org.opencontainers.image.source="https://github.com/usebruno/bruno"
LABEL org.opencontainers.image.description="Bruno CLI - Open source IDE for exploring and testing APIs"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.title="Bruno CLI"
LABEL org.opencontainers.image.version="${BRUNO_VERSION}"
LABEL org.opencontainers.image.url="https://www.usebruno.com"
LABEL org.opencontainers.image.documentation="https://docs.usebruno.com/bru-cli/overview"
ENV LC_ALL="en_US.UTF-8" \
LANG="en_US.UTF-8" \
LANGUAGE="en_US.UTF-8"
# If BRUNO_VERSION is provided, validate it is a valid semver before installing
RUN if [ -n "$BRUNO_VERSION" ]; then \
if ! echo "$BRUNO_VERSION" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+$"; then \
echo "\033[0;31mA valid semver Bruno version is required in the BRUNO_VERSION build-arg (e.g. 1.16.0)\033[0m"; \
exit 1; \
fi; \
fi && \
npm install -g @usebruno/cli${BRUNO_VERSION:+@${BRUNO_VERSION}}
WORKDIR /bruno
USER node
ENTRYPOINT ["bru"]
CMD []

View File

@@ -0,0 +1,27 @@
# Bruno CLI — Alpine
Alpine Linux variant of the Bruno CLI Docker image.
**Base image:** `node:22-alpine`
## Building
```bash
docker build -t usebruno/cli:alpine ./images/alpine
# with specific Bruno CLI version
docker build \
--build-arg BRUNO_VERSION=3.3.0 \
-t usebruno/cli:3.3.0-alpine \
./images/alpine
```
## Usage
```bash
# Run a collection
docker run --rm -v $(pwd):/bruno usebruno/cli:alpine run --env staging
# with pinned version
docker run --rm -v $(pwd):/bruno usebruno/cli:3.3.0-alpine run --env staging
```

View File

@@ -0,0 +1,34 @@
FROM node:22-slim
LABEL maintainer="Bruno <engineering@usebruno.com>"
ARG BRUNO_VERSION
ENV BRUNO_VERSION=${BRUNO_VERSION}
LABEL org.opencontainers.image.source="https://github.com/usebruno/bruno"
LABEL org.opencontainers.image.description="Bruno CLI - Open source IDE for exploring and testing APIs"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.title="Bruno CLI"
LABEL org.opencontainers.image.version="${BRUNO_VERSION}"
LABEL org.opencontainers.image.url="https://www.usebruno.com"
LABEL org.opencontainers.image.documentation="https://docs.usebruno.com/bru-cli/overview"
ENV LC_ALL="en_US.UTF-8" \
LANG="en_US.UTF-8" \
LANGUAGE="en_US.UTF-8"
# If BRUNO_VERSION is provided, validate it is a valid semver before installing
RUN if [ -n "$BRUNO_VERSION" ]; then \
if ! echo "$BRUNO_VERSION" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+$"; then \
echo "\033[0;31mA valid semver Bruno version is required in the BRUNO_VERSION build-arg (e.g. 1.16.0)\033[0m"; \
exit 1; \
fi; \
fi && \
npm install -g @usebruno/cli${BRUNO_VERSION:+@${BRUNO_VERSION}}
WORKDIR /bruno
USER node
ENTRYPOINT ["bru"]
CMD []

View File

@@ -0,0 +1,27 @@
# Bruno CLI — Debian
Debian slim variant of the Bruno CLI Docker image.
**Base image:** `node:22-slim`
## Building
```bash
docker build -t usebruno/cli:debian ./images/debian
# with specific Bruno CLI version
docker build \
--build-arg BRUNO_VERSION=3.3.0 \
-t usebruno/cli:3.3.0-debian \
./images/debian
```
## Usage
```bash
# Run a collection
docker run --rm -v $(pwd):/bruno usebruno/cli:debian run --env staging
# with pinned version
docker run --rm -v $(pwd):/bruno usebruno/cli:3.3.0-debian run --env staging
```

View File

@@ -0,0 +1,135 @@
#!/bin/sh
# Smoke tests for Bruno CLI Docker image
# Usage: ./smoke-test.sh <image> [collection-abs-path] [run-target] [env-name]
# Examples:
# ./smoke-test.sh usebruno/cli:alpine
# ./smoke-test.sh usebruno/cli:alpine /abs/path/to/collection echo Prod
set -e
IMAGE=$1
COLLECTION_PATH=$2
RUN_TARGET=${3:-.}
COLLECTION_ENV=$4
if [ -z "$IMAGE" ]; then
echo "Usage: $0 <image> [collection-abs-path] [run-target] [env-name]"
exit 1
fi
echo "Running smoke tests for image: $IMAGE"
echo "---"
# Test 1 - bru is installed and returns a version
echo "Test 1: bru --version"
VERSION=$(docker run --rm "$IMAGE" --version)
echo "$VERSION"
if [ -z "$VERSION" ]; then
echo " FAIL: no version output"
exit 1
fi
echo " PASS"
# Test 2 - container runs as non-root user "node"
echo "Test 2: non-root user"
USER=$(docker run --rm --entrypoint whoami "$IMAGE")
echo "$USER"
if [ "$USER" != "node" ]; then
echo " FAIL: expected 'node', got '$USER'"
exit 1
fi
echo " PASS"
# Test 3 - working directory is /bruno
echo "Test 3: working directory"
DIR=$(docker run --rm --entrypoint pwd "$IMAGE")
echo "$DIR"
if [ "$DIR" != "/bruno" ]; then
echo " FAIL: expected '/bruno', got '$DIR'"
exit 1
fi
echo " PASS"
# Test 4 - bru help works
echo "Test 4: bru --help"
docker run --rm "$IMAGE" --help > /dev/null
echo " PASS"
# Test 5 (optional) - run an actual Bruno collection
#
# Engine-vs-content semantics:
# This test validates that bru can execute a collection end-to-end
# (parser, request layer, JS sandbox, assertion engine, summary output).
# It does NOT require every test/assertion in the collection to pass.
#
# Success: bru reaches the run summary AND at least 1 request passed.
# Failure: bru did not emit a summary (engine broken) OR every request failed
# (suggests image-level breakage rather than incidental test flakes).
#
# Any individual test/request failures are surfaced as warnings (full bru
# output kept above for trace) so the team can investigate without blocking
# the publish.
if [ -n "$COLLECTION_PATH" ]; then
if [ ! -d "$COLLECTION_PATH" ]; then
echo "Test 5: FAIL - collection path not found: $COLLECTION_PATH"
exit 1
fi
# Build the optional --env argument as positional params so it remains
# properly quoted when passed to docker run (avoids word-splitting issues
# if COLLECTION_ENV ever contains spaces or special characters).
set --
if [ -n "$COLLECTION_ENV" ]; then
set -- --env "$COLLECTION_ENV"
fi
echo "Test 5: bru run $RUN_TARGET${COLLECTION_ENV:+ --env $COLLECTION_ENV}"
echo "----- bru run output -----"
set +e
# Use --mount instead of -v so Windows-style paths (e.g. C:\repo\collection)
# don't collide with -v's host:container colon separator.
OUTPUT=$(docker run --rm \
--mount "type=bind,source=$COLLECTION_PATH,target=/bruno" \
"$IMAGE" \
run "$RUN_TARGET" "$@" 2>&1)
EXIT=$?
set -e
echo "$OUTPUT"
echo "----- bru run output end (exit=$EXIT) -----"
# Locate bru's end-of-run summary, supporting both output formats:
# New (table): "Requests | 14 (12 Passed, 2 Failed)"
# Legacy (line): "Requests: 14, Passed: 12, Failed: 2"
# Reject ANSI/box-drawing chars by grep'ing for the request-count pattern.
SUMMARY_REQ=$(echo "$OUTPUT" | grep -E "[0-9]+[[:space:]]+Passed,[[:space:]]+[0-9]+[[:space:]]+Failed" | head -1)
if [ -z "$SUMMARY_REQ" ]; then
# Fall back to legacy "Requests: N, Passed: N, Failed: N" form
SUMMARY_REQ=$(echo "$OUTPUT" | grep -E "Requests:[[:space:]]+[0-9]+,[[:space:]]+Passed:[[:space:]]+[0-9]+,[[:space:]]+Failed:[[:space:]]+[0-9]+" | head -1)
fi
if [ -z "$SUMMARY_REQ" ]; then
echo " FAIL: bru did not emit a run summary - engine likely crashed"
exit 1
fi
PASSED=$(echo "$SUMMARY_REQ" | grep -oE "([0-9]+[[:space:]]+Passed|Passed:[[:space:]]+[0-9]+)" | head -1 | grep -oE "[0-9]+")
FAILED=$(echo "$SUMMARY_REQ" | grep -oE "([0-9]+[[:space:]]+Failed|Failed:[[:space:]]+[0-9]+)" | head -1 | grep -oE "[0-9]+")
PASSED=${PASSED:-0}
FAILED=${FAILED:-0}
echo " Summary: $SUMMARY_REQ"
if [ "$PASSED" -ge 1 ]; then
if [ "$FAILED" -gt 0 ]; then
echo " PASS (with warnings: $FAILED request(s) failed - see output above for details)"
# Surface as a GitHub Actions warning annotation when run in CI
echo "::warning::Smoke Test 5 ($IMAGE): $FAILED request(s) failed in collection '$RUN_TARGET' env=$COLLECTION_ENV. $PASSED passed. Image marked OK. Review bru output in this job's log."
else
echo " PASS (all $PASSED request(s) passed)"
fi
else
echo " FAIL: 0 requests passed (Passed=$PASSED, Failed=$FAILED) - check image and network"
exit 1
fi
fi
echo "---"
echo "All smoke tests passed for $IMAGE"