From a2fb257a77af217910d536e347bb4a01b9ba1fdc Mon Sep 17 00:00:00 2001 From: Scot Lunsford Date: Wed, 20 May 2026 16:28:46 -0500 Subject: [PATCH] Add live reload development workflow --- docker/README.md | 18 ++++- docker/example-setup/compose.dev.yaml | 71 ++++++++++++++------ docker/setup-workbench.sh | 94 ++++++++++++++++++++------- 3 files changed, 140 insertions(+), 43 deletions(-) diff --git a/docker/README.md b/docker/README.md index 718dcc0..832dcb4 100644 --- a/docker/README.md +++ b/docker/README.md @@ -14,15 +14,29 @@ After running the script, deploy with: ```bash cd ../instances/your-instance-name -# deploy with docker compose +# Deploy with published images docker compose up -d -# or deploy in development mode +# Build a developer-mode stack without active file watching docker compose up -d --build + +# Build, run, and watch source changes in developer mode +docker compose up --watch --build + +# Or start watching after the developer-mode stack is already running +docker compose watch ``` Access Workbench at +Developer mode requires Docker Compose 2.22.0 or newer for Compose Watch and sibling component repositories next to this deployment repository: + +- `attack-workbench-frontend` +- `attack-workbench-rest-api` +- `attack-workbench-taxii-server` when TAXII is enabled + +In developer mode, Compose Watch syncs component source into named Docker volumes mounted at the application source paths. This avoids writing watched files directly into image layers, which is important on Docker Desktop environments that use Enhanced Container Isolation/rootless runtimes. The frontend runs Angular's dev server, the REST API runs its development watcher, and TAXII runs Nest's watch mode. Dependency manifest and build configuration changes such as `package.json`, `package-lock.json`, or Angular TypeScript config files rebuild the affected service image. + Full variable descriptions and examples are available in [docs/configuration](docs/configuration.md). For source builds or TAXII setup, see [docs/deployment](docs/deployment.md). diff --git a/docker/example-setup/compose.dev.yaml b/docker/example-setup/compose.dev.yaml index 75366ac..e4702fd 100644 --- a/docker/example-setup/compose.dev.yaml +++ b/docker/example-setup/compose.dev.yaml @@ -2,53 +2,86 @@ services: frontend: image: attack-workbench-frontend - build: ../../../attack-workbench-frontend + build: + context: ../../../attack-workbench-frontend + target: dev + volumes: !override + - frontend_src:/workspace/src develop: watch: # Sync source files for hot-reload - action: sync path: ../../../attack-workbench-frontend/src - target: /app/src - ignore: - - node_modules/ + target: /workspace/src + initial_sync: true + # Rebuild on Angular build configuration changes + - action: rebuild + path: ../../../attack-workbench-frontend/angular.json + - action: rebuild + path: ../../../attack-workbench-frontend/tsconfig.json + - action: rebuild + path: ../../../attack-workbench-frontend/tsconfig.app.json # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-frontend/package.json + - action: rebuild + path: ../../../attack-workbench-frontend/package-lock.json rest-api: image: attack-workbench-rest-api - build: ../../../attack-workbench-rest-api + build: + context: ../../../attack-workbench-rest-api + target: dev + volumes: !override + - rest_api_app:/usr/src/app/app + - rest_api_bin:/usr/src/app/bin + - rest_api_resources:/usr/src/app/resources develop: watch: # Sync app source files - action: sync path: ../../../attack-workbench-rest-api/app target: /usr/src/app/app - ignore: - - node_modules/ - # Restart on config changes + initial_sync: true + # Sync server entrypoint and config files + - action: sync + path: ../../../attack-workbench-rest-api/bin + target: /usr/src/app/bin + initial_sync: true + # Sync generated runtime config file from this compose instance - action: sync+restart - path: ../../../attack-workbench-rest-api/resources - target: /usr/src/app/resources + path: ./configs/rest-api/rest-api-service-config.json + target: /usr/src/app/resources/rest-api-service-config.json + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-rest-api/package.json + - action: rebuild + path: ../../../attack-workbench-rest-api/package-lock.json taxii: image: attack-workbench-taxii-server - build: ../../../attack-workbench-taxii-server + build: + context: ../../../attack-workbench-taxii-server + target: development + volumes: + - taxii_src:/app/src develop: watch: # Sync source files - action: sync - path: ../../../attack-workbench-taxii-server/taxii - target: /app/taxii - ignore: - - node_modules/ - # Sync config files and restart - - action: sync+restart - path: ../../../attack-workbench-taxii-server/config - target: /app/config + path: ../../../attack-workbench-taxii-server/src + target: /app/src + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-taxii-server/package.json + - action: rebuild + path: ../../../attack-workbench-taxii-server/package-lock.json + +volumes: + frontend_src: + rest_api_app: + rest_api_bin: + rest_api_resources: + taxii_src: diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 6396b90..bd981ed 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -681,18 +681,30 @@ generate_frontend_override() { frontend: image: $REPO_FRONTEND - build: ../../../$REPO_FRONTEND + build: + context: ../../../$REPO_FRONTEND + target: dev + volumes: !override + - frontend_src:/workspace/src develop: watch: # Sync source files for hot-reload - action: sync path: ../../../$REPO_FRONTEND/src - target: /app/src - ignore: - - node_modules/ + target: /workspace/src + initial_sync: true + # Rebuild on Angular build configuration changes + - action: rebuild + path: ../../../$REPO_FRONTEND/angular.json + - action: rebuild + path: ../../../$REPO_FRONTEND/tsconfig.json + - action: rebuild + path: ../../../$REPO_FRONTEND/tsconfig.app.json # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_FRONTEND/package.json + - action: rebuild + path: ../../../$REPO_FRONTEND/package-lock.json EOF } @@ -702,16 +714,28 @@ generate_rest_api_dev_override() { rest-api: image: $REPO_REST_API - build: ../../../$REPO_REST_API + build: + context: ../../../$REPO_REST_API + target: dev EOF # Add custom cert volumes if enabled if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then cat << 'EOF' - volumes: + volumes: !override + - rest_api_app:/usr/src/app/app + - rest_api_bin:/usr/src/app/bin + - rest_api_resources:/usr/src/app/resources - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} +EOF + else + cat << 'EOF' + volumes: !override + - rest_api_app:/usr/src/app/app + - rest_api_bin:/usr/src/app/bin + - rest_api_resources:/usr/src/app/resources EOF fi @@ -723,15 +747,22 @@ EOF - action: sync path: ../../../$REPO_REST_API/app target: /usr/src/app/app - ignore: - - node_modules/ - # Restart on config changes + initial_sync: true + # Sync server entrypoint and config files + - action: sync + path: ../../../$REPO_REST_API/bin + target: /usr/src/app/bin + initial_sync: true + # Sync generated runtime config file from this compose instance - action: sync+restart - path: ../../../$REPO_REST_API/resources - target: /usr/src/app/resources + path: ./configs/rest-api/rest-api-service-config.json + target: /usr/src/app/resources/rest-api-service-config.json + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_REST_API/package.json + - action: rebuild + path: ../../../$REPO_REST_API/package-lock.json EOF } @@ -808,22 +839,23 @@ generate_taxii_override() { taxii: image: $REPO_TAXII - build: ../../../$REPO_TAXII + build: + context: ../../../$REPO_TAXII + target: development + volumes: + - taxii_src:/app/src develop: watch: # Sync source files - action: sync - path: ../../../$REPO_TAXII/taxii - target: /app/taxii - ignore: - - node_modules/ - # Sync config files and restart - - action: sync+restart - path: ../../../$REPO_TAXII/config - target: /app/config + path: ../../../$REPO_TAXII/src + target: /app/src + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_TAXII/package.json + - action: rebuild + path: ../../../$REPO_TAXII/package-lock.json EOF } @@ -850,6 +882,21 @@ EOF if [[ $ENABLE_INSIGHTS =~ ^[Yy]$ ]]; then generate_insights_override >> "$override_file" fi + + cat >> "$override_file" << 'EOF' + +volumes: + frontend_src: + rest_api_app: + rest_api_bin: + rest_api_resources: +EOF + + if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then + cat >> "$override_file" << 'EOF' + taxii_src: +EOF + fi else # Production mode - only add rest-api if custom certs are enabled if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then @@ -895,7 +942,7 @@ show_configuration_summary() { echo " REST API: $config_root/configs/rest-api/.env" echo " REST API: $config_root/configs/rest-api/rest-api-service-config.json" if [[ $enable_taxii =~ ^[Yy]$ ]]; then - echo " TAXII: $config_root/configs/taxii/config/.env" + echo " TAXII: $config_root/configs/taxii/config/dev.env" fi if [[ $enable_insights =~ ^[Yy]$ ]]; then echo " Grafana: $config_root/configs/grafana/.env" @@ -936,9 +983,12 @@ show_deployment_instructions() { info "To deploy your instance:" echo " cd $instance_dir" if [[ $dev_mode =~ ^[Yy]$ ]]; then + echo " docker compose up --watch --build" + echo "" + info "For detached developer mode without active watching:" echo " docker compose up -d --build" echo "" - info "For hot-reloading in developer mode, use watch:" + info "To start watching an already-running developer-mode stack:" echo " docker compose watch" else echo " docker compose up -d"