Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/phpunit-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ jobs:
-w /app \
--entrypoint sh \
durable-workflow-server-corpus-validator \
-lc 'set -eu; tar --no-same-owner -xf - -C /app; cp -a /app/workflow-package /workflow; php scripts/ci/prepare-release-workflow-composer-metadata.php; composer update durable-workflow/workflow --with-dependencies --minimal-changes --no-install --no-scripts --no-autoloader --no-interaction --no-progress --prefer-dist; composer install --no-interaction --no-progress --prefer-dist; git --version; vendor/bin/phpunit --version; git rev-parse --verify "$CORPUS_BASE_REF^{commit}"; php scripts/ci/check-worker-openapi-evolution.php "$CORPUS_BASE_REF"; vendor/bin/phpunit tests/Feature tests/Unit/NexusContractTest.php tests/Unit/CodecRegressionCorpusTest.php tests/Unit/PhpunitFeatureWorkflowContractTest.php tests/Unit/OpenApiDocumentEvolutionTest.php tests/Unit/NamespaceConformanceRunnerContractTest.php tests/Unit/NamespaceRuntimeContractTest.php tests/Unit/SchedulesConformanceRunnerContractTest.php tests/Unit/SchedulesRuntimeContractTest.php tests/Unit/ServerParitySdkDirectionResponseSummaryTest.php tests/Unit/ServerParitySdkQueryTransportTest.php tests/Unit/ServerParitySdkUpdateTransportTest.php tests/Unit/ServerParityLockPressureRetryTest.php tests/Unit/ServerParityManyUpdatesRetryTest.php --no-progress --colors=never; vendor/bin/phpunit tests/Unit/ReleaseImageContinuationTest.php tests/Unit/ReleaseImageImmutabilityGuardTest.php --no-progress --colors=never; vendor/bin/phpunit tests/Unit/ReleaseImagePublishWorkflowContractTest.php --filter test_php_security_audits_fail_before_release_publication --no-progress --colors=never; python3 scripts/ci/validate-regression-corpus.py --base-ref "$CORPUS_BASE_REF" --verify-counterfactual'
-lc 'set -eu; tar --no-same-owner -xf - -C /app; cp -a /app/workflow-package /workflow; php scripts/ci/prepare-release-workflow-composer-metadata.php; composer update durable-workflow/workflow --with-dependencies --minimal-changes --no-install --no-scripts --no-autoloader --no-interaction --no-progress --prefer-dist; composer install --no-interaction --no-progress --prefer-dist; git --version; vendor/bin/phpunit --version; git rev-parse --verify "$CORPUS_BASE_REF^{commit}"; php scripts/ci/check-worker-openapi-evolution.php "$CORPUS_BASE_REF"; vendor/bin/phpunit tests/Feature tests/Unit/NexusContractTest.php tests/Unit/NativePublishedComposeContractTest.php tests/Unit/PublishedServerAutoSelectionTest.php tests/Unit/CodecRegressionCorpusTest.php tests/Unit/PhpunitFeatureWorkflowContractTest.php tests/Unit/OpenApiDocumentEvolutionTest.php tests/Unit/NamespaceConformanceRunnerContractTest.php tests/Unit/NamespaceRuntimeContractTest.php tests/Unit/SchedulesConformanceRunnerContractTest.php tests/Unit/SchedulesRuntimeContractTest.php tests/Unit/ServerParitySdkDirectionResponseSummaryTest.php tests/Unit/ServerParitySdkQueryTransportTest.php tests/Unit/ServerParitySdkUpdateTransportTest.php tests/Unit/ServerParityLockPressureRetryTest.php tests/Unit/ServerParityManyUpdatesRetryTest.php --no-progress --colors=never; vendor/bin/phpunit tests/Unit/ReleaseImageContinuationTest.php tests/Unit/ReleaseImageImmutabilityGuardTest.php --no-progress --colors=never; vendor/bin/phpunit tests/Unit/ReleaseImagePublishWorkflowContractTest.php --filter test_php_security_audits_fail_before_release_publication --no-progress --colors=never; python3 scripts/ci/validate-regression-corpus.py --base-ref "$CORPUS_BASE_REF" --verify-counterfactual'

- name: Select production image qualification for changed build inputs
id: image_inputs
Expand Down
13 changes: 6 additions & 7 deletions .github/workflows/published-compose.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
name: Published Compose Smoke

on:
pull_request:
paths:
- docker-compose.published.yml
- scripts/smoke-published-compose.sh
- .github/workflows/published-compose.yml
workflow_dispatch:
inputs:
server_tag:
description: "Published durableworkflow/server tag to test when DW_SERVER_IMAGE is not set"
required: false
default: "0.2"
server_image:
description: "Full image reference or digest override, for example ghcr.io/durable-workflow/server:<exact-version>"
description: "Native image reference or digest to test instead of docker-compose.published.yml's default"
required: false
default: ""

Expand Down Expand Up @@ -52,10 +53,8 @@ jobs:

- name: Set Compose image selector
env:
INPUT_SERVER_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.server_tag || '0.2' }}
INPUT_SERVER_IMAGE: ${{ github.event_name == 'workflow_dispatch' && inputs.server_image || '' }}
run: |
echo "DW_SERVER_TAG=${INPUT_SERVER_TAG:-0.2}" >> "$GITHUB_ENV"
if [ -n "$INPUT_SERVER_IMAGE" ]; then
echo "DW_SERVER_IMAGE=$INPUT_SERVER_IMAGE" >> "$GITHUB_ENV"
fi
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ jobs:
DW_SERVER_IMAGE: ${{ env.DOCKERHUB_IMAGE }}@${{ steps.exact.outputs.image_digest }}
DW_SERVER_PLATFORM: linux/amd64
DW_PUBLISHED_COMPOSE_PROFILE: local
DW_PUBLISHED_COMPOSE_FILE: docker-compose.php.yml
DW_PUBLISHED_COMPOSE_PROJECT: release-compose-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
SERVER_PORT: "0"
DW_AUTH_TOKEN: published-compose-token
Expand Down Expand Up @@ -364,7 +365,7 @@ jobs:
DW_PUBLISHED_COMPOSE_PROJECT: release-compose-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
run: >-
docker compose -p "$DW_PUBLISHED_COMPOSE_PROJECT"
-f release-source/docker-compose.published.yml down -v --remove-orphans
-f release-source/docker-compose.php.yml down -v --remove-orphans
>/dev/null 2>&1 || true

- name: Verify published protocol catalog convergence
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/replay-query-concurrent-http.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
- 'composer.lock'
- 'config/dw-bounded-growth.php'
- 'docker/**'
- 'docker-compose.published.yml'
- 'docker-compose.php.yml'
- 'Dockerfile'
- 'scripts/regression/apache-module-preflight.sh'
- 'scripts/regression/replay-query-concurrent-http.sh'
Expand All @@ -30,7 +30,7 @@ on:
- 'composer.lock'
- 'config/dw-bounded-growth.php'
- 'docker/**'
- 'docker-compose.published.yml'
- 'docker-compose.php.yml'
- 'Dockerfile'
- 'scripts/regression/apache-module-preflight.sh'
- 'scripts/regression/replay-query-concurrent-http.sh'
Expand Down Expand Up @@ -93,5 +93,5 @@ jobs:
run: |
docker rm -f "${DW_REPLAY_QUERY_COMPOSE_PROJECT}-nonroot-http" >/dev/null 2>&1 || true
docker compose -p "$DW_REPLAY_QUERY_COMPOSE_PROJECT" \
-f docker-compose.published.yml down -v --remove-orphans \
-f docker-compose.php.yml down -v --remove-orphans \
>/dev/null 2>&1 || true
1 change: 1 addition & 0 deletions Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
!composer.lock
!docker-compose.yml
!docker-compose.published.yml
!docker-compose.php.yml
!docker-compose.small-cluster.yml
!docker-compose.dedicated-matching.yml
!docker-compose.failover-rehearsal.yml
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ For the 21 October 2026 critical PHP update, follow the
[PHP runtime security notice](docs/security/php-runtime-2026-10-21.md) and verify
the patched runtime in every deployed role.

The published Compose stack starts Server with MySQL, Redis, a queue worker,
and the scheduler. It bootstraps the database and the default namespace before
accepting traffic.
The published Compose stack starts the native Server with MySQL. Native is a
single process: it creates its schema and the default namespace at startup and
runs its own maintenance, so there is no Redis, queue worker or scheduler. The
PHP 2.x stack remains available as
[`docker-compose.php.yml`](docker-compose.php.yml) for installations that pin
it; see the [native takeover runbook](docs/rust-server-upgrade-takeover.md) to
move an existing PHP database to native.

```bash
curl -fsSLO https://raw.githubusercontent.com/durable-workflow/server/main/docker-compose.published.yml
Expand All @@ -41,9 +45,13 @@ curl -H "Authorization: Bearer $DW_AUTH_TOKEN" \
http://localhost:8080/api/cluster/info
```

This development configuration uses a single compatibility token. Production
deployments should use role-scoped worker, operator, and administrator
credentials and pin the Server image by version or digest. See the
The file requires `DW_AUTH_TOKEN` and publishes the API on `127.0.0.1` only;
set `SERVER_BIND_ADDRESS` to a protected private interface for remote access and
terminate HTTPS at a reverse proxy. This development configuration uses the
shared `dev-token`. Production
deployments must set a strong `DW_AUTH_TOKEN` (native accepts it for every
role), should add role-scoped worker, operator, and administrator tokens, and
should pin the Server image by version or digest. See the
[self-hosting reference](docs/server-reference.md) for SQLite, production
Compose, authentication, backup, upgrade, API, and configuration guidance.
For a tested recovery sequence, see the
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.dedicated-matching.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: durable-workflow-server

# Compose override that enables the dedicated matching-role daemon shape on
# top of `docker-compose.published.yml`. The default shape runs the in-worker
# top of `docker-compose.php.yml`. The default shape runs the in-worker
# matching-role wake on every Looping event and an unscoped scheduler repair
# pass for remote SDK queues. Larger deployments can opt execution-only workers
# out of the wake and let a single dedicated daemon own the broad sweep.
#
# Usage:
# docker compose \
# -f docker-compose.published.yml \
# -f docker-compose.php.yml \
# -f docker-compose.dedicated-matching.yml \
# up
#
Expand Down
161 changes: 161 additions & 0 deletions docker-compose.php.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: durable-workflow-server

# The PHP 2.x Durable Workflow Server: HTTP server, queue worker, scheduler,
# bootstrap job, MySQL and Redis. From 3.0.0 the published Compose file,
# docker-compose.published.yml, runs the native server; this file keeps the
# PHP 2.x stack for installations that pin it, and for the published-artifact
# conformance runners that qualify PHP releases.
#
# Generated by scripts/ci/sync-source-release.mjs. Do not edit the fallback.
x-server-image: &server-image ${DW_SERVER_IMAGE:-durableworkflow/server:${DW_SERVER_TAG:-2.11.8}}

x-server-environment: &server-environment
APP_NAME: "Durable Workflow Server"
APP_ENV: ${APP_ENV:-local}
DW_SERVER_KEY: ${DW_SERVER_KEY:-}
APP_VERSION: ${APP_VERSION:-${DW_SERVER_TAG:-2.11.8}}
APP_DEBUG: ${APP_DEBUG:-false}
LOG_CHANNEL: ${LOG_CHANNEL:-stderr}
LOG_LEVEL: ${LOG_LEVEL:-info}
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE:-durable_workflow}
DB_USERNAME: ${DB_USERNAME:-workflow}
DB_PASSWORD: ${DB_PASSWORD:-workflow}
REDIS_HOST: redis
QUEUE_CONNECTION: redis
CACHE_STORE: redis
DW_TASK_DISPATCH_MODE: ${DW_TASK_DISPATCH_MODE:-poll}
DW_AUTH_DRIVER: ${DW_AUTH_DRIVER:-token}
DW_AUTH_TOKEN: ${DW_AUTH_TOKEN:-dev-token}
DW_PRINCIPAL_TOKENS: ${DW_PRINCIPAL_TOKENS:-${WORKFLOW_SERVER_PRINCIPAL_TOKENS:-}}
DW_RUNTIME_CREDENTIALS_ENABLED: ${DW_RUNTIME_CREDENTIALS_ENABLED:-${WORKFLOW_SERVER_RUNTIME_CREDENTIALS_ENABLED:-false}}
DW_WORKER_TOKEN: ${DW_WORKER_TOKEN:-}
DW_OPERATOR_TOKEN: ${DW_OPERATOR_TOKEN:-}
DW_ADMIN_TOKEN: ${DW_ADMIN_TOKEN:-}
DW_AUTH_BACKWARD_COMPATIBLE: ${DW_AUTH_BACKWARD_COMPATIBLE:-true}
DW_EXTERNAL_PAYLOAD_S3_ACCESS_KEY_ID: ${DW_EXTERNAL_PAYLOAD_S3_ACCESS_KEY_ID:-}
DW_EXTERNAL_PAYLOAD_S3_SECRET_ACCESS_KEY: ${DW_EXTERNAL_PAYLOAD_S3_SECRET_ACCESS_KEY:-}
DW_EXTERNAL_PAYLOAD_S3_SESSION_TOKEN: ${DW_EXTERNAL_PAYLOAD_S3_SESSION_TOKEN:-}
DW_EXTERNAL_PAYLOAD_S3_REGION: ${DW_EXTERNAL_PAYLOAD_S3_REGION:-us-east-1}
DW_EXTERNAL_PAYLOAD_S3_BUCKET: ${DW_EXTERNAL_PAYLOAD_S3_BUCKET:-}
DW_EXTERNAL_PAYLOAD_S3_ENDPOINT: ${DW_EXTERNAL_PAYLOAD_S3_ENDPOINT:-}
DW_EXTERNAL_PAYLOAD_S3_USE_PATH_STYLE_ENDPOINT: ${DW_EXTERNAL_PAYLOAD_S3_USE_PATH_STYLE_ENDPOINT:-false}

services:
bootstrap:
image: *server-image
platform: ${DW_SERVER_PLATFORM:-}
command: ["server-bootstrap"]
healthcheck:
disable: true
environment:
<<: *server-environment
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy

server:
image: *server-image
platform: ${DW_SERVER_PLATFORM:-}
ports:
- "${SERVER_PORT:-8080}:8080"
environment:
<<: *server-environment
DW_SERVER_TOPOLOGY_SHAPE: standalone_server
DW_SERVER_PROCESS_CLASS: server_http_node
depends_on:
bootstrap:
condition: service_completed_successfully
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/ready"]
interval: 10s
timeout: 5s
retries: 3

worker:
image: *server-image
platform: ${DW_SERVER_PLATFORM:-}
command: php artisan queue:work --sleep=1 --tries=3 --max-time=3600
restart: unless-stopped
healthcheck:
test: ["CMD", "server-process-healthcheck", "worker"]
interval: 5s
timeout: 3s
retries: 3
start_period: 5s
environment:
<<: *server-environment
DW_SERVER_TOPOLOGY_SHAPE: standalone_server
DW_SERVER_PROCESS_CLASS: worker_node
depends_on:
bootstrap:
condition: service_completed_successfully
server:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy

scheduler:
image: *server-image
platform: ${DW_SERVER_PLATFORM:-}
init: true
# Remote SDK queues can differ from the Laravel queue worker's Looping scope.
# The dedicated matching override disables this unscoped scheduler pass.
command: >-
sh -c 'while true; do php artisan schedule:evaluate --limit=100 --json; if [ "$$DW_V2_MATCHING_ROLE_QUEUE_WAKE" != "false" ] && [ "$$DW_V2_MATCHING_ROLE_QUEUE_WAKE" != "0" ]; then php artisan workflow:v2:repair-pass --json; fi; php artisan activity:timeout-enforce --limit=100; php artisan history:prune --limit=100; sleep 10; done'
healthcheck:
test: ["CMD", "server-process-healthcheck", "scheduler"]
interval: 5s
timeout: 3s
retries: 3
start_period: 5s
environment:
<<: *server-environment
DW_SERVER_TOPOLOGY_SHAPE: standalone_server
DW_SERVER_PROCESS_CLASS: scheduler_node
depends_on:
bootstrap:
condition: service_completed_successfully
mysql:
condition: service_healthy
redis:
condition: service_healthy

mysql:
image: mysql:${MYSQL_VERSION:-8.0}
environment:
MYSQL_DATABASE: ${DB_DATABASE:-durable_workflow}
MYSQL_USER: ${DB_USERNAME:-workflow}
MYSQL_PASSWORD: ${DB_PASSWORD:-workflow}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root}
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 20

redis:
image: redis:${REDIS_VERSION:-7-alpine}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5

volumes:
mysql_data:
redis_data:
Loading
Loading