A Dapr Workflow demo on the Dapr JS SDK (Node.js + TypeScript + Express). The runtime surface schedules durable workflows that enrich a payload and query PostgreSQL through a Dapr binding, with Redis-backed state for crash-safe replay (verified by killing the app mid-flight); the delivery surface covers a distroless multi-stage image, a three-layer test pyramid (Vitest unit + integration, shell-driven e2e / e2e-dapr / e2e-durability), and a supply-chain–hardened GitHub Actions pipeline (gitleaks, Trivy filesystem + image scan, hadolint, OWASP ZAP DAST, cosign keyless OIDC signing) on an mise-pinned, Renovate-managed toolchain.
| Component | Technology |
|---|---|
| Language | TypeScript (pinned in package.json) |
| Runtime | Node.js (LTS major pinned in .nvmrc) |
| Web framework | Express |
| Workflow engine | Dapr Workflow via @dapr/dapr (pinned in package.json) |
| State store | Redis (via Dapr state component, image pinned by digest in docker-compose.yaml) |
| Data binding | PostgreSQL (via Dapr binding component, image pinned by digest) |
| Container CLI | Docker or Podman (auto-detected by the Makefile) |
| Testing | Vitest (unit + integration), shell-driven e2e against the production image |
| Linting | ESLint + typescript-eslint, hadolint for Dockerfile, mermaid-cli + PlantUML for diagrams |
| Formatting | Prettier |
| Security | gitleaks, Trivy filesystem + image scan, pnpm audit, OWASP ZAP DAST |
| CI/CD | GitHub Actions, Renovate, act (local CI), cosign keyless image signing |
make deps # bootstrap mise + install every pinned tool (node, pnpm, act, dapr, gitleaks, hadolint, trivy, container-structure-test); check podman + git
cp .env.example .env # optional: override any port/host/timeout (see Configuration below)
make dapr-init # initialize Dapr (one-time; starts Redis, placement, scheduler)
make up # start PostgreSQL + Redis via Podman Compose
make start # build and start API server with Dapr sidecar (foreground)
# -> http://localhost:3000Every operator-tunable value (ports, hosts, Dapr app-id, healthcheck timings,
readiness timeouts) lives in the committed .env.example — the single source
of truth. Copy it to .env (gitignored) and override as needed; Docker Compose
auto-loads .env and the Makefile -includes it (its ?= values are the
fallback when .env is absent). Nothing
is hardcoded. To run alongside another Postgres/Redis already on the default
ports, override them — make up POSTGRES_PORT=5433 REDIS_PORT=6380 && make start
— and make check-ports names any container still holding a needed port. DB
credentials are coupled to the Dapr component YAML (documented in .env.example
for reference; change them there and in the component together).
Every tool below (except make, podman, git) is pinned in .mise.toml / .nvmrc and installed in one step by make deps (which bootstraps mise if missing).
| Tool | Pinned in | Purpose |
|---|---|---|
| GNU Make | system | Build orchestration |
| mise | bootstrapped | Tool version manager — reads .nvmrc + .mise.toml |
| Node.js | .nvmrc |
JavaScript runtime |
| pnpm | .mise.toml |
Package manager |
| Dapr CLI | .mise.toml |
Dapr sidecar management |
| Renovate | .mise.toml |
Local Renovate dry-run via make renovate |
| act | .mise.toml |
Run GitHub Actions locally |
| Trivy | .mise.toml |
Filesystem CVE / secret / misconfig scanner |
| gitleaks | .mise.toml |
Secret scanner |
| hadolint | .mise.toml |
Dockerfile linter, invoked by make lint |
| Podman | system | Container runtime for PostgreSQL/Redis |
| Git | system | Version control |
Exact pinned versions live in the source-of-truth files (
.nvmrc,.mise.toml,package.json,docker-compose.yaml,Dockerfile). Renovate keeps them up to date automatically.
Install all required dependencies:
make deps- Express API + WorkflowRuntime run in the same Node process. The API handlers are thin — they schedule workflows via
DaprWorkflowClientover gRPC, and the sidecar's scheduler streams activity work items back to the runtime over the same gRPC connection. - Dapr Sidecar (
daprd) is the orchestrator. The runtime version is pinned viaDAPR_RUNTIME_VERSIONin theMakefile(Renovate-tracked). All state persistence, activity dispatch, and component I/O go through it. - Redis stores durable workflow state. Killing the app container mid-run and restarting it replays the workflow from Redis-persisted state — verified end-to-end by
make e2e-durability. - PostgreSQL is not used directly by the app. The
fetchPostgresDataActivityPOSTs a SQL query to the sidecar's binding HTTP API; the sidecar resolves it via thebindings.postgrescomponent and returns rows. See ADR-0001: Query PostgreSQL via Dapr binding for the rationale.
The C4 Context and Container diagrams are C4-PlantUML sources in
docs/diagrams/(c4-context.puml,c4-container.puml); regenerate the committed PNGs withmake diagrams. The sequence diagrams below are inline Mermaid that GitHub renders directly.
sequenceDiagram
autonumber
participant U as API Consumer
participant A as Express API<br/>(+ WorkflowRuntime)
participant D as Dapr Sidecar
participant P as PostgreSQL
U->>+A: POST /process-payload
A->>+D: scheduleNewWorkflow (gRPC)
D-->>-A: workflow id
A-->>-U: 202 { id }
rect rgb(245,245,245)
Note over D,A: Async activity dispatch via gRPC streaming
D->>A: delayActivity(ms)
A-->>D: ok
D->>A: modifyPayloadActivity(payload)
A-->>D: enriched payload
D->>A: fetchPostgresDataActivity
A->>D: POST /v1.0/bindings/postgres-db
D->>+P: SELECT * FROM users
P-->>-D: rows
D-->>A: rows
A-->>D: dbData
end
Note over D: Workflow state persisted to Redis after each activity
U->>+A: GET /workflow/:id/status
A->>+D: getWorkflowState (gRPC)
D-->>-A: runtime status + output
A-->>-U: 200 { status, output }
The delayActivity step defaults to 30 s (simulating a long-running request); tests and e2e-dapr override it to 0 via the request body. While a workflow is mid-flight, GET /workflow/:id/status returns RUNNING; once all activities complete, the same endpoint returns COMPLETED with the enriched JSON payload.
Validated end-to-end by make e2e-durability: the workflow is scheduled with a 15 s delay, the app container is killed mid-flight, restarted, and the workflow still completes with the full enriched payload (including the Postgres binding result) — replayed from Redis-persisted state.
sequenceDiagram
autonumber
participant U as Test Driver
participant A as Express API<br/>(killed, then restarted)
participant D as Dapr Sidecar<br/>(survives restart)
participant R as Redis
U->>A: POST /process-payload { delayMs: 15000 }
A->>D: scheduleNewWorkflow
D->>R: persist workflow state
D-->>A: workflow id
A-->>U: 202 { id }
D->>A: delayActivity(15000)
Note over A: ⏱ activity in flight
rect rgb(255,235,235)
Note over U,A: Test kills the app container at t≈5s
U-x A: docker kill
end
rect rgb(235,250,235)
Note over U,A: Test restarts the app container — sidecar untouched
U->>A: docker run (same image)
A->>D: re-register WorkflowRuntime (gRPC)
end
Note over D,R: Sidecar resumes from Redis state, replays remaining activities
D->>A: delayActivity (resumed)
A-->>D: ok
D->>A: modifyPayloadActivity
A-->>D: enriched payload
D->>A: fetchPostgresDataActivity
A->>D: POST /v1.0/bindings/postgres-db
D-->>A: dbData
A-->>D: dbData
D->>R: persist final state
U->>A: GET /workflow/:id/status
A->>D: getWorkflowState
D-->>A: COMPLETED + output (processed:true, dbData)
A-->>U: 200 { status: "COMPLETED", output }
The Dapr sidecar is left running across the kill — only the app container is replaced. This mirrors the production failure mode where an app pod crashes and is rescheduled while the sidecar (or its replacement) keeps Redis state durable.
All ports are exposed as Makefile variables (make var=value overrides for non-default deployments) and matching *_PORT env vars.
| Service | Default | Variable | Protocol | Purpose |
|---|---|---|---|---|
| Express API | 3000 | PORT |
HTTP | REST endpoints |
| Dapr sidecar | 3500 | DAPR_HTTP_PORT |
HTTP | Binding calls from activities |
| Dapr sidecar | 50001 | DAPR_GRPC_PORT |
gRPC | WorkflowClient / WorkflowRuntime |
| Dapr scheduler | 50006 | DAPR_SCHEDULER_PORT |
gRPC | Workflow scheduling |
| PostgreSQL | 5432 | POSTGRES_PORT |
TCP | Database backend |
| Redis | 6379 | REDIS_PORT |
TCP | Dapr state store |
| Test image | 3100 | TEST_HOST_PORT |
HTTP | Host port the prod image binds for e2e / dast |
The host portion of every URL is $(HOST) (default localhost), also overridable. The e2e shell scripts (e2e/e2e-dapr.sh, e2e/e2e-durability.sh) pick free ports from the kernel's ephemeral range when APP_PORT/DAPR_*_PORT aren't already set, so parallel runs don't collide.
POSTGRES_PORT / REDIS_PORT are honored end-to-end — the Compose port mapping (${POSTGRES_PORT:-5432}:5432) and the Dapr binding components. Because Dapr component metadata has no {env:VAR} substitution, make start renders the committed components into a gitignored runtime dir (make render-components) with the current ports substituted, then points dapr run --resources-path at it. So you can run the whole stack alongside another Postgres already holding 5432:
export POSTGRES_PORT=5433 # any free host port
make up && make start # compose publishes 5433; the sidecar dials localhost:5433src/
api-server.ts Entrypoint: imports app, calls listen, wires SIGINT
app.ts Express app, lazy-init Dapr workflow client (exported for tests)
data-request-workflow.ts Workflow definition and activities
__tests__/
*.test.ts Unit tests (Vitest + supertest)
*.integration.test.ts Integration tests (require running Dapr stack)
e2e/
e2e-dapr.sh Full-stack e2e: production image + Dapr sidecar
e2e-durability.sh Durability e2e: kill app mid-flight, assert resume
components/ Dapr component configs (local dev)
dapr/ci/ Dapr component configs (CI)
db/ SQL schema and seed data
docker-compose.yaml PostgreSQL + Redis for local development
# Terminal 1 -- start infrastructure and server
make up # start PostgreSQL + Redis via Podman Compose
make start # build and start API server with Dapr sidecar (foreground)
# Terminal 2 -- verify
make check-db # run database health check workflow
make check-workflow # trigger a test workflow and poll the resultmake start-no-dapr
curl http://localhost:3000/make stop # stop Dapr sidecar and API server
make down # stop PostgreSQL + Redis containers| Method | Path | Description |
|---|---|---|
GET |
/ |
Health check |
POST |
/process-payload |
Schedule a new workflow; returns { id } (202). Empty body returns 400. Optional delayMs in body. |
GET |
/workflow/:id/status |
Poll workflow state; output present when status == "COMPLETED". Unknown id returns 404. |
GET |
/db-health |
Schedule a workflow and wait up to 10s for DB result |
Capture the generated workflow id into a shell variable so every subsequent poll reuses it:
# Schedule and capture the id in one shot
WF_ID=$(curl -s -X POST http://localhost:3000/process-payload \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "data": {"key1": "value1"}}' \
| jq -r .id)
echo "$WF_ID"
# -> 82236756-4f38-4b5f-9796-a1268184561e
# Poll (while the 30s delay activity is running)
curl -s "http://localhost:3000/workflow/$WF_ID/status" | jq .While running:
{
"id": "82236756-4f38-4b5f-9796-a1268184561e",
"status": "RUNNING",
"createdAt": "2026-04-16T16:34:44.118Z",
"lastUpdatedAt": "2026-04-16T16:34:47.139Z"
}After completion (output is present):
{
"id": "82236756-4f38-4b5f-9796-a1268184561e",
"status": "COMPLETED",
"output": "{\"name\":\"John Doe\",\"processed\":true,...}",
"createdAt": "2026-04-16T16:34:44.118Z",
"lastUpdatedAt": "2026-04-16T16:35:21.199Z"
}Poll until done using the same variable:
until curl -s "http://localhost:3000/workflow/$WF_ID/status" | jq -e '.status == "COMPLETED"' > /dev/null; do
sleep 2
done
curl -s "http://localhost:3000/workflow/$WF_ID/status" | jq .| Stage | Command | Output | Notes |
|---|---|---|---|
| Compile TS | make build |
dist/*.js + sourcemaps |
Invoked by make smoke, make image-build, and the CI build job |
| Container | make image-build |
Local Docker image dapr-nodejs-workflow:<tag> (multi-stage, distroless) |
Stage 1 deps → Stage 2 build → Stage 2b prod-deps → Stage 3 distroless nonroot runtime |
| Filesystem CVE scan | make trivy-fs |
Trivy report (CRITICAL/HIGH blocking) | Part of make static-check; CI runs the same in the static-check job |
| Image CVE scan | (CI only) | Trivy aquasecurity/trivy-action report |
Runs in the docker job on tag pushes only, blocks on CRITICAL/HIGH |
The image is signed with cosign keyless OIDC by digest at tag-push time only — see CI/CD § Pre-push image hardening below.
make test # run Vitest unit tests (activity logic + supertest HTTP)
make test-watch # run unit tests in watch modemake integration-test auto-provisions everything it needs — it starts PostgreSQL + Redis (up, after a check-ports preflight) and the API server + Dapr sidecar in the background (start-bg), then runs the suite:
make dapr-init # one-time: initialize Dapr (placement + scheduler + Redis)
make integration-test # provisions infra + backgrounded sidecar, then runs the testsThe backgrounded stack is left running afterward; make stop + make down to clean up. (In CI the job provisions its own PostgreSQL service container and sidecar, so integration-test there runs only the suite.)
make e2e runs the production Docker image standalone and verifies the Dapr-unreachable error path (shallow e2e, no sidecar). make e2e-dapr builds the image and runs it alongside a real Dapr sidecar to assert a workflow COMPLETES end-to-end. make e2e-durability additionally kills the app container mid-flight and asserts the workflow resumes from Redis-persisted state.
make ci # run static-check, test, build locally
make ci-run # run GitHub Actions workflow locally via act (requires Docker)The
integration-testGitHub Actions job uses service containers not supported byact. Test integration locally with the steps above.
Run make help to see all targets in one list.
| Target | Description |
|---|---|
make help |
List all available tasks |
make deps |
Bootstrap mise (once) and install every pinned tool (node from .nvmrc; pnpm, act, dapr, gitleaks, hadolint, trivy, container-structure-test from .mise.toml); check podman + git |
make install |
Install npm dependencies (uses --frozen-lockfile when CI=true) |
make clean |
Remove build artifacts and node_modules |
| Target | Description |
|---|---|
make build |
Build TypeScript to dist/ |
make format |
Auto-fix formatting with Prettier |
make format-check |
Check formatting without modifying files |
make lint |
Run Prettier check, ESLint, TypeScript noEmit, and hadolint |
make vulncheck |
Audit dependencies for known vulnerabilities |
make secrets |
Scan for hardcoded secrets with gitleaks |
make trivy-fs |
Scan filesystem for vulnerabilities, secrets, and misconfigurations |
make deps-prune |
Show unused/redundant Node.js dependencies |
make deps-prune-check |
Verify no prunable dependencies (CI gate) |
make components-check |
Drift gate: fails if components/*.yaml and dapr/ci/*.yaml differ beyond password/comments |
make render-check |
Gate: asserts render-components substitutes an overridden POSTGRES_PORT/REDIS_PORT into the rendered Dapr components (so the DB-port override provably reaches the sidecar) |
make mermaid-lint |
Validate Mermaid diagrams in README.md + CLAUDE.md via pinned minlag/mermaid-cli |
make diagrams |
Render the C4 PlantUML sources (docs/diagrams/*.puml) to committed PNGs via pinned plantuml/plantuml |
make diagrams-clean |
Remove rendered diagram artefacts (docs/diagrams/out/) |
make diagrams-check |
Drift gate: re-render the C4 diagrams and fail if the committed PNGs differ from current .puml source |
make check-node-alignment |
Drift gate: fails if the Node major disagrees across .nvmrc, .mise.toml, and the Dockerfile |
make static-check |
Composite quality gate (check-node-alignment + lint + vulncheck + secrets + trivy-fs + deps-prune-check + components-check + render-check + diagrams-check + mermaid-lint) |
| Target | Description |
|---|---|
make test |
Run unit tests |
make test-watch |
Run unit tests in watch mode |
make integration-test |
Run integration tests (locally auto-provisions infra + backgrounded sidecar) |
make smoke |
HTTP smoke test against built server (no Dapr) |
| Target | Description |
|---|---|
make dapr-init |
Initialize Dapr in local environment (stops conflicting Redis if needed) |
make up |
Start PostgreSQL and Redis via Podman Compose (preflights check-ports, waits until both accept connections) |
make down |
Stop infrastructure services and remove containers |
make check-ports |
Fail early (naming the offending container) if a needed host port is already bound — compose ports for up, app + sidecar for start/start-bg, the image host port for e2e/smoke/dast |
make postgres-start |
Start only the PostgreSQL Compose service (env-driven; alternative to make up) |
make postgres-stop |
Stop PostgreSQL Podman container |
| Target | Description |
|---|---|
make start |
Build and start the API server with Dapr sidecar |
make start-bg |
Build and start the API server + Dapr sidecar in the background (used by integration-test) |
make stop |
Stop the Dapr sidecar and API server |
make start-no-dapr |
Build and start the API server without Dapr sidecar |
make run |
Alias for start-no-dapr |
make check-workflow |
Trigger a test workflow and print the result |
make check-db |
Run the database health check endpoint |
| Target | Description |
|---|---|
make check |
Run full local verification (static-check, test, build; static-check runs lint which runs prettier --check) |
make ci |
Run local CI pipeline (static-check, test, build; static-check runs lint which runs prettier --check) |
make ci-run |
Run GitHub Actions workflow locally via act |
make ci-run-tag |
Run GitHub Actions workflow locally with a tag event (exercises docker job) |
make release VERSION=vX.Y.Z |
Validate VERSION format, then run tag-release to commit, tag, and push |
The
ci-seed-db,ci-dapr-start,render-components,render-ci-components,docker-smoke-test,dast-scan,docker-verify-manifest,check-version, andtag-releasetargets are internal/auto-invoked helpers —render-componentsis invoked transitively viamake start/start-bg, andtag-release/check-versionviamake release; theci-*,render-ci-components, anddocker-*targets are called exclusively from CI (service-container provisioning, component rendering, pre-push image gating, and image manifest verification). They are not intended for direct local use — usemake up+make startlocally andmake release VERSION=vX.Y.Zfor tagging.
| Target | Description |
|---|---|
make image-build |
Build the production Docker image (multi-stage) |
make image-run |
Run the Docker image standalone (no Dapr) |
make image-stop |
Stop the running image container |
make image-structure-test |
Assert the image's structure (non-root uid, entrypoint/cmd, exposed port, files) via container-structure-test |
make e2e-compose |
Compose-config e2e: boot PostgreSQL + Redis via Compose, assert the seeded DB is queryable + Redis round-trips, tear down |
make e2e |
Shallow e2e: production image standalone, verifies the Dapr-unreachable error path |
make e2e-dapr |
Full-stack e2e: production image + Dapr sidecar, asserts a workflow COMPLETES end-to-end |
make e2e-durability |
Workflow replay e2e: kills the app mid-flight, asserts the workflow resumes from Redis-persisted state |
make dast |
ZAP baseline DAST scan against the built image |
| Target | Description |
|---|---|
make update |
Update dependencies to latest allowed versions |
make upgrade |
Upgrade dependencies to latest versions (ignoring ranges) |
make renovate |
Run Renovate locally in dry-run mode |
make renovate-validate |
Validate Renovate configuration |
GitHub Actions runs on every push to main, version tags (v*), and pull requests. The workflow is reusable via workflow_call.
| Job | Depends on | Steps |
|---|---|---|
| changes | — | dorny/paths-filter detector — emits code=true for code changes, code=false for doc-only (*.md, docs/**, image assets; CLAUDE.md is re-included as project config). All heavy jobs gate on this output, so doc-only PRs skip them and ci-pass reports green via skipped-jobs. Replaces trigger-level paths-ignore (Rulesets-incompatible). |
| static-check | changes | make static-check (check-node-alignment, Prettier check, ESLint, tsc --noEmit, hadolint, pnpm audit, gitleaks, Trivy fs scan, depcheck, components-check, render-check, diagrams-check, mermaid-lint) |
| build | changes, static-check | make build + make smoke (HTTP smoke test against the built server) |
| test | changes, static-check | make test (Vitest unit tests — activity logic, checkPort, supertest HTTP) |
| e2e-compose | changes, build, test | make e2e-compose (boots the Compose stack, asserts the seeded DB is queryable + Redis round-trips, tears down; covers the Compose config the service-container jobs don't). Skipped under act. |
| e2e | changes, build, test | make image-structure-test (container-structure-test: non-root uid, entrypoint/cmd, exposed port, files) + make e2e (shallow: standalone image, validates health endpoint + Dapr-unreachable error path) |
| e2e-dapr | changes, build, test | make ci-seed-db + build image + ./e2e/e2e-dapr.sh (production image alongside dapr run sidecar, asserts workflow COMPLETES). Skipped under act. |
| integration-test | changes, build, test | make ci-seed-db, make build, make ci-dapr-start, make integration-test (PostgreSQL service container + Dapr CLI pinned via .mise.toml). Skipped under act. |
| e2e-durability | changes, build, test | make ci-seed-db + build image + ./e2e/e2e-durability.sh (schedules a workflow with a 15s delay, kills the app container mid-flight, restarts it, asserts COMPLETED from Redis-persisted state). Skipped under act. |
| dast | changes, build, test | Tag pushes only (refs/tags/): build image via cache-from: type=gha, make docker-smoke-test, cached ZAP image, make dast-scan, upload report artifact. Skipped on non-tag pushes/PRs and under act. |
| docker | changes, static-check, build, test | Tag pushes only (refs/tags/): the whole job — amd64 image build, Trivy image CVE scan (CRITICAL/HIGH blocking), boot-marker smoke, GHCR push, cosign signing (amd64-only; arm64 dropped for speed). Skipped on non-tag pushes/PRs (per-push image coverage comes from e2e/e2e-dapr) |
| ci-pass | all of the above | Gate job: fails if any upstream job failed |
The docker job runs the following gates before any image is pushed to GHCR. Any gate failure blocks the release.
| # | Gate | Catches | Tool |
|---|---|---|---|
| 1 | Build local single-arch image | Build regressions on the runner architecture | docker/build-push-action with load: true |
| 2 | Trivy image scan (CRITICAL/HIGH blocking) | CVEs in the base image, OS packages, build layers | aquasecurity/trivy-action with image-ref: |
| 3 | Smoke test | Image boots correctly on its own (Node.js boot-marker grep) | make docker-smoke-test |
| 4 | Image build + push | Publishes linux/amd64 (arm64 dropped for build speed) |
docker/build-push-action |
| 5 | Image manifest verification | Asserts the image has linux/amd64 and no unknown/unknown (catches attestation leaks) |
make docker-verify-manifest |
| 6 | Cosign keyless OIDC signing | Sigstore signature on the manifest digest | sigstore/cosign-installer + cosign sign |
The dast job runs in parallel with the docker job and performs an additional security scan:
| Gate | Catches | Tool |
|---|---|---|
| OWASP ZAP baseline scan | Missing security headers, misconfigs, info leaks | make dast-scan (OWASP ZAP -I = warn only, report uploaded as artifact) |
Buildkit in-manifest attestations (provenance + sbom) are disabled so the image stays free of unknown/unknown platform entries, which lets GHCR's Packages UI render the "OS / Arch" tab. Cosign keyless signing still provides the Sigstore signature for supply-chain verification. Images are built for linux/amd64 only — arm64 was dropped to keep the docker job fast (the QEMU-emulated arm64 build dominated its wall-clock).
Verify a published image's signature:
cosign verify ghcr.io/andriykalashnykov/dapr-nodejs-workflow:<tag> \
--certificate-identity-regexp 'https://github\.com/AndriyKalashnykov/dapr-nodejs-workflow/\.github/workflows/ci\.yml@refs/tags/v.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe cleanup-runs.yml workflow runs weekly to delete old workflow runs and stale caches via the native gh CLI.
Renovate keeps dependencies up to date with platform automerge enabled. Tool versions pinned in the Makefile are tracked via inline # renovate: comments.
| Name | Type | Used by | How to set |
|---|---|---|---|
ACT |
Variable | integration-test, e2e-dapr, e2e-durability, e2e-compose, dast jobs |
Set to true under nektos/act to skip jobs that need service containers or docker-in-docker bind mounts (integration-test, e2e-dapr, e2e-durability, e2e-compose, dast). Set via Settings > Secrets and variables > Actions > Variables tab > New repository variable. |
GITHUB_TOKEN is provisioned automatically by GitHub Actions; no manual setup is needed.
Contributions welcome — open a PR.
This project is licensed under the MIT License — see LICENSE for details.

