diff --git a/packages/bruno-cli/docker/README.md b/packages/bruno-cli/docker/README.md new file mode 100644 index 000000000..3a363ed97 --- /dev/null +++ b/packages/bruno-cli/docker/README.md @@ -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 | +| `` | `usebruno/cli:3.3.0` | alpine | +| `` | `usebruno/cli:3.3` | alpine | +| `` | `usebruno/cli:3` | alpine | +| `-alpine` | `usebruno/cli:3.3.0-alpine` | alpine | +| `-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` | diff --git a/packages/bruno-cli/docker/images/alpine/Dockerfile b/packages/bruno-cli/docker/images/alpine/Dockerfile new file mode 100644 index 000000000..50a1fbad9 --- /dev/null +++ b/packages/bruno-cli/docker/images/alpine/Dockerfile @@ -0,0 +1,34 @@ +FROM node:22-alpine + +LABEL maintainer="Bruno " + +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 [] diff --git a/packages/bruno-cli/docker/images/alpine/README.md b/packages/bruno-cli/docker/images/alpine/README.md new file mode 100644 index 000000000..e4a08a09b --- /dev/null +++ b/packages/bruno-cli/docker/images/alpine/README.md @@ -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 +``` diff --git a/packages/bruno-cli/docker/images/debian/Dockerfile b/packages/bruno-cli/docker/images/debian/Dockerfile new file mode 100644 index 000000000..7b97f169a --- /dev/null +++ b/packages/bruno-cli/docker/images/debian/Dockerfile @@ -0,0 +1,34 @@ +FROM node:22-slim + +LABEL maintainer="Bruno " + +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 [] diff --git a/packages/bruno-cli/docker/images/debian/README.md b/packages/bruno-cli/docker/images/debian/README.md new file mode 100644 index 000000000..abf967d0e --- /dev/null +++ b/packages/bruno-cli/docker/images/debian/README.md @@ -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 +``` diff --git a/packages/bruno-cli/docker/smoke-test.sh b/packages/bruno-cli/docker/smoke-test.sh new file mode 100755 index 000000000..2bf0b08e8 --- /dev/null +++ b/packages/bruno-cli/docker/smoke-test.sh @@ -0,0 +1,135 @@ +#!/bin/sh +# Smoke tests for Bruno CLI Docker image +# Usage: ./smoke-test.sh [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 [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"