Development Environment for Crucible - a cybersecurity training and simulation platform developed by Carnegie Mellon University's Software Engineering Institute (SEI).
- Getting Started
- Using the Dev Container
- Claude Code
- Memory Optimization
- Database Seeding and Backup
- Moodle Configuration
- Library Development
crucible-development is a Development-Containers-based solution that uses Aspire to orchestrate the various components of Crucible, along with supporting resources like an identity provider (Keycloak), a Postgres database server, and Moodle.
If your environment is already set up, skip to Using the Dev Container.
To use any dev container, you'll need to run Docker on your machine. Docker Desktop is a great way to get started.
If you're on a Windows machine, Docker's consumption of your host machine's memory and storage may be managed by WSL2. These will automatically scale to a percentage of your system's available resources, so you typically don't need to do any additional configuration.
If you're on Mac/Linux using Docker Desktop, you'll need to manually adjust these limits. In Docker Desktop, go to Settings -> Resources. We recommend the following minimums:
- Memory Limit: 16GB
- Disk Usage Limit: 120GB
This will vary based on usage. Running all applications simultaneously may require more memory. See Memory Optimization for tips.
For details on how to add root CA certificates, see the Custom Certs Docs.
The Aspire project uses dotnet dev-certs to generate development certificates. Additional development certificates, including a CA, are generated at container build time via the postcreate.sh script for testing helm deployments in minikube. These certificates are git ignored and placed in the .devcontainer/dev-certs directory.
This repo is designed exclusively for use within the dev container. The scripts and Aspire orchestration assume dev container paths and configurations and will not work outside of it.
To see all Crucible repositories in the VS Code file explorer, you need to open the workspace file:
- Click on
crucible-dev.code-workspacein the VS Code file explorer - Click the "Open Workspace" button that appears in the bottom-right corner
Alternatively, use File > Open Workspace from File and select crucible-dev.code-workspace.
You can confirm you're in the workspace when the VS Code title bar shows "crucible-dev (Workspace)". Without this, the /mnt/data/crucible directory (containing all cloned repositories) won't appear in the file explorer. This can be done before or after opening the repo inside the dev container.
Several VS Code launch profiles are pre-configured for different development scenarios. To use them:
- Click the Run and Debug icon in the left panel (or press
Ctrl+Shift+D) - Use the dropdown at the top to select a profile
- Press F5 (or the green play button) to launch
Each profile runs a different subset of services depending on what you're working on. Some examples:
- Default - Launches most services (resource-intensive)
- Player, Blueprint, Caster, Steamfitter, etc. - Focused on a single application
- Exercise, TTX - Multi-app profiles for specific workflows
- Moodle, Moodle-Xdebug - Moodle development with optional PHP debugging
Pressing F5 without changing the dropdown launches the Default profile (or your previously selected profile). Be aware that the Default profile launches many applications and uses significant system resources.
The default admin user credentials are:
- Username:
admin - Password:
admin
The dev container includes Claude Code, Anthropic's CLI for Claude, configured to use AWS Bedrock. There are two setup methods that can be used to authenticate to AWS. Select the one that fits your use case.
-
Copy the example credentials file:
cp .devcontainer/.aws/credentials.example .devcontainer/.aws/credentials
-
Edit
.devcontainer/.aws/credentialsand add your AWS credentials:[default] aws_access_key_id = <AWS Access Key for Bedrock> aws_secret_access_key = <AWS Secret Key for Bedrock>
-
Build or rebuild the dev container
The credentials file is mounted to /home/vscode/.aws/credentials inside the container and is excluded from git via .devcontainer/.gitignore.
-
Copy the example config file:
cp .devcontainer/.aws/config.example .devcontainer/.aws/config
-
Edit
.devcontainer/.aws/configand add your AWS account information:[sso-session crucible-sso] sso_start_url = https://<YOUR-ORG>.awsapps.com/start sso_region = <your-region> sso_registration_scopes = sso:account:access [profile default] sso_session = crucible-sso sso_account_id = <your-account-id> sso_role_name = <your-role> region = <your-region> output = json
-
Build or rebuild the dev container
-
Run the aws-sso-login.sh script
scripts/aws-sso-login.sh
The config file is mounted to /home/vscode/.aws/config inside the container and is excluded from git via .devcontainer/.gitignore.
Once the container is running with valid credentials, run claude in the terminal to start Claude Code.
The Crucible development environment includes 30+ microservices and can be memory-intensive. Several optimizations are configured to reduce memory usage:
The Intelephense PHP language server is disabled by default (configured in .devcontainer/devcontainer.json) to save approximately 337MB of memory. See Moodle PHP IntelliSense for instructions on enabling it when working on PHP code.
The Crucible AppHost supports running Angular UIs in two modes to optimize memory usage during development:
Dev Mode: Full ng serve with hot reload (~1.5GB per UI)
- Use for your primary development app
- Instant code changes without rebuild
- Full debugging capabilities
Production Mode: Lightweight production build server (~90MB per UI)
- Use for supporting apps during integration testing
- Saves ~1.4GB per UI compared to dev mode
- Requires manual rebuild when code changes
The configuration system uses a two-tier approach:
1. Primary App Selection (.env/*.env files - committed to git)
Task-specific .env files define which app is the primary development focus using boolean flags:
# .env/blueprint.env - Blueprint development task
Launch__Blueprint=true # Dev mode with hot reload (~1.5GB)
# .env/player.env - Player development task
Launch__Player=true # Dev mode with hot reload (~1.5GB)
# .env/ttx.env - Multi-app development task
Launch__Player=true
Launch__Steamfitter=true
Launch__Cite=true
Launch__Gallery=true
Launch__Blueprint=trueBoolean flag behavior:
true= Launch in dev mode (ng serve with hot reload)falseor omitted = App is off (not launched)
2. Supporting Apps (.env/*.env or appsettings.Development.json - local overrides)
Add supporting apps in production or dev mode using appsettings.Development.json (git-ignored):
{
"Launch": {
"Prod": ["Gallery", "Cite", "Lrsql", "PGAdmin"],
"Dev": ["Steamfitter"]
}
}Or directly in .env files for team-shared configurations:
# All team members working on this task need these supporting apps
Launch__Blueprint=true # Primary dev app
Launch__Gallery=true # Supporting dev app
Launch__Cite=true # Supporting dev appConfiguration Precedence (highest to lowest):
- Boolean flag in
.envfile =true→ Dev mode - App name in
Devarray → Dev mode - App name in
Prodarray → Production mode - Otherwise → Off
Memory Savings:
Running 5 UIs in production mode instead of dev mode saves approximately 6-8GB of memory, allowing you to run comprehensive integration tests while actively developing on a single primary application.
Example Workflow:
- Select your task in VS Code's launch picker (e.g., "Blueprint Task")
- The
.env/blueprint.envfile launches Blueprint in dev mode - Add supporting apps to your local
appsettings.Development.json:{ "Launch": { "Prod": ["Player", "Gallery"], // Prod mode for testing "Dev": [] // Additional dev mode apps } } - Restart the task to apply changes
Moodle Integration:
Moodle automatically configures itself based on which Crucible services are running:
- The Crucible block only shows links to services that are currently running
- URLs are only configured for enabled services
- This prevents timeouts and slow page loads when services aren't available
Angular UIs (support dev/prod modes):
- Player (player-ui, player-vm-ui, player-vm-console-ui)
- Caster (caster-ui)
- Alloy (alloy-ui)
- TopoMojo (topomojo-ui)
- Steamfitter (steamfitter-ui)
- CITE (cite-ui)
- Gallery (gallery-ui)
- Blueprint (blueprint-ui)
- Gameboard (gameboard-ui)
Containers (support prod mode only via boolean flags or Prod array):
- Moodle (with optional Xdebug - see Moodle Configuration below)
- Lrsql (Learning Record Store for xAPI)
- Misp (threat intelligence platform)
- PGAdmin (database administration)
- Docs (MkDocs documentation server)
These examples use blueprint as the database name. Replace with the appropriate database name for your use case.
- Create a
db-dumpsfolder under the project root:mkdir -p db-dumps
- Copy your
.dumpfile into thedb-dumpsfolder
- Drop the existing database using PGAdmin
- Create a new empty database with the same name using PGAdmin
- Copy the dump file into the container and restore it:
docker cp db-dumps/blueprint.dump crucible-postgres:/tmp/blueprint.dump docker exec -it crucible-postgres /bin/bash /usr/lib/postgresql/17/bin/psql --username=postgres blueprint < /tmp/blueprint.dump exit
docker exec -it crucible-postgres /bin/bash
pg_dump -U postgres blueprint > /tmp/blueprint.dump
exit
docker cp crucible-postgres:/tmp/blueprint.dump db-dumps/blueprint.dumpMoodle will be configured using files located in scripts/ and resources/moodle/.
When starting for the first time, Moodle will make a copy of some core files that will
be copied into mounts on the dev container's file system so that they are accessible for
debugging with xdebug. These files will be mounted alongside our repos under the folder
/mnt/data/crucible/moodle/moodle-core/. The xAPI logstore plugin will also be configured
automatically as will one default Moodle course with no activities within it.
Two Moodle task configurations are available:
.env/moodle.env- Moodle without Xdebug (faster, for general development/testing).env/moodle-xdebug.env- Moodle with Xdebug enabled (for PHP debugging)
Use the appropriate task based on whether you need to debug PHP code. Xdebug has significant performance overhead, so only enable it when actively debugging.
Moodle automatically configures the Crucible block based on which services are running:
- Only enabled services have their API URLs configured
- Disabled services have empty URLs (prevents connection timeouts)
- The block only displays links to running services
- This is configured dynamically via environment variables passed from AppHost.cs
This ensures fast dashboard loading regardless of which services are running in your current task.
Moodle will be configured for oauth automatically. The oauth admin user has an email address set and the Moodle client has a hard-coded secret.
After Moodle starts for the first time, login using the oauth admin user account and it will than have an account on Moodle. Make the oauth admin account a site admin by either logging in as local admin and using the Site Administration menu, or, simply restart the Moodle container via the Aspire dashboard and when the container restarts, the oauth admin user will be added to the list of site admins. Please note that every time the container restarts the list of site admins will be reset to the local admin and the oauth admin account. When the oauth admin account has been made a site admin, login with it and navigate to the oauth server settings under Site Administration, Server, and connect the system account. This will enable our plugins to communicate with the various Crucible applications.
To configure Moodle to work with Crucible, oauth must be configured on Moodle, the service account must be connected, and the user must be logged an with oauth.
To configure Moodle to work with TopoMojo, login to TopoMojo, generate an API key, and
add that API key to the Moodle crucible plugin's configuration in the Moodle UI or in the
script post_configure.sh.
Moodle plugins are stored in a hierarchical directory structure that mirrors the Moodle container layout:
/mnt/data/crucible/moodle/
├── mod/topomojo/ # mod_topomojo plugin
├── mod/crucible/ # mod_crucible plugin
├── admin/tool/lptmanager/ # tool_lptmanager plugin
├── blocks/crucible/ # block_crucible plugin
└── moodle-core/ # Core Moodle files (theme, lib, etc.)
To add new Moodle plugin repositories, add them to scripts/repos.json or scripts/repos.local.json. The clone script automatically maps plugin names to the correct hierarchical paths. Everything else is automatic:
- Clone script creates hierarchical directory structure
- AppHost.cs dynamically reads repos.json + repos.local.json and creates bind mounts
- xdebug_filter.php is auto-generated during devcontainer setup
- launch.json uses a general pathMapping that covers all plugins automatically
No manual configuration needed! Just add the plugin to repos.json or repos.local.json and rebuild.
For private repositories (internal Git servers, private GitHub repos, or third-party plugins that shouldn't be committed to this public repo), use the local repository configuration pattern:
-
Create
scripts/repos.local.jsonfrom the example template:cp scripts/repos.local.json.example scripts/repos.local.json
-
Add your private repositories to
scripts/repos.local.json:{ "groups": [ { "name": "moodle", "repos": [ { "name": "logstore_xapi", "url": "https://github.com/xAPI-vle/moodle-logstore_xapi" }, { "name": "prototype_plugin", "url": "https://git.internal.example.com/moodle/prototype_plugin.git" } ] } ] } -
Run the clone script:
./scripts/clone-repos.sh
The repos.local.json file is git-ignored, so your private repository URLs remain private. The clone script automatically merges this with the public repos.json configuration.
Supported URL formats:
- HTTPS:
https://github.com/org/repo.git - SSH:
git@github.com:org/repo.git - Internal Git server:
https://git.internal.example.com/project/repo.git - Personal access tokens:
https://token@github.com/org/repo.git
If you need to customize a public plugin (like logstore_xapi) that can't be forked privately on GitHub, create a private mirror on your internal Git server:
Initial Setup:
-
Clone the upstream repository:
cd /tmp git clone https://github.com/xAPI-vle/moodle-logstore_xapi.git cd moodle-logstore_xapi
-
Create an empty private repo on your internal Git server (via web UI)
- Repository name:
moodle-logstore_xapi - Do NOT initialize with README/gitignore
- Repository name:
-
Configure remotes:
# Rename GitHub remote to upstream git remote rename origin upstream # Add your internal Git server as origin git remote add origin ssh://git@git.internal.example.com/youruser/moodle-logstore_xapi.git # Verify remotes git remote -v # Should show: # origin ssh://git@git.internal.example.com/... (your internal Git server) # upstream https://github.com/xAPI-vle/... (original GitHub)
-
Push to your private repository:
# Rename master to main (modern convention) git branch -m master main # Push all branches and tags git push -u origin main git push origin --all git push origin --tags
-
Update
repos.local.jsonto use your internal Git server URL:{ "groups": [{ "name": "moodle", "repos": [{ "name": "logstore_xapi", "url": "ssh://git@git.internal.example.com/youruser/moodle-logstore_xapi.git" }] }] } -
Clone into development environment:
cd /workspaces/crucible-development ./scripts/clone-repos.sh
Daily Workflow:
cd /mnt/data/crucible/moodle/admin/tool/log/store/xapi
# Create feature branch for custom work
git checkout -b feature/branch-name
# Make your changes
# ... edit files ...
# Commit and push to your internal repository
git add .
git commit -m "commit message"
git push -u origin feature/branch-name
# Merge to main when ready
git checkout main
git merge feature/branch-name
git push origin mainSyncing with Upstream (pull latest from GitHub):
# Fetch latest from upstream GitHub
git fetch upstream
# Switch to your main branch
git checkout main
# Merge upstream changes
git merge upstream/master
# Note: Many GitHub repos still use 'master' (not 'main')
# Resolve any conflicts with your customizations
# Push updated main to your internal repository
git push origin main
# Update your feature branch
git checkout feature/branch-name
git rebase main # or: git merge mainQuick Commands:
# See what's new on upstream before merging
git fetch upstream
git log main..upstream/master --oneline
# Pull and merge in one step
git checkout main
git pull upstream master
git push origin mainThe xdebug filter configuration is automatically generated from repos.json and repos.local.json:
- Template:
Crucible.AppHost/resources/moodle/xdebug_filter.php.template(checked into git, core paths only) - Generated file:
Crucible.AppHost/resources/moodle/xdebug_filter.php(git-ignored, includes all plugins) - Generated by:
scripts/generate-xdebug-filter.sh(runs during devcontainer postcreate) - When to regenerate: Run
./scripts/generate-xdebug-filter.shmanually if you add/remove plugins after container creation
The generated file is git-ignored to prevent accidentally committing private plugin paths from repos.local.json. The Dockerfile uses the generated file if it exists, or falls back to the template.
The script automatically maps Moodle plugin naming conventions to their container paths:
mod_*→/var/www/html/mod/*tool_*→/var/www/html/admin/tool/*logstore_*→/var/www/html/admin/tool/log/store/*block_*→/var/www/html/blocks/*- And all other standard Moodle plugin types
With the hierarchical directory structure, launch.json uses simplified pathMappings:
{
// Specific paths for moodle-core (must come first)
"/var/www/html/theme": "/mnt/data/crucible/moodle/moodle-core/theme",
"/var/www/html/lib": "/mnt/data/crucible/moodle/moodle-core/lib",
"/var/www/html/admin/cli": "/mnt/data/crucible/moodle/moodle-core/admin/cli",
"/var/www/html/ai/provider": "/mnt/data/crucible/moodle/moodle-core/ai/provider",
"/var/www/html/ai/classes": "/mnt/data/crucible/moodle/moodle-core/ai/classes",
// General catch-all for ALL plugins
"/var/www/html": "/mnt/data/crucible/moodle"
}This means you no longer need to update launch.json when adding new plugins! The general pathMapping automatically covers all plugins because the host and container directory structures now match. Only moodle-core paths need specific mappings.
Fully automatic workflow:
- Add plugin to
repos.jsonorrepos.local.json - Run
./scripts/clone-repos.sh(or rebuild devcontainer) - Rebuild/restart containers
Everything else (bind mounts, xdebug filter, path mappings) is handled automatically!
If you have an existing development environment with the old flat structure (mod_topomojo, tool_lptmanager, etc.), use the migration script:
./scripts/migrate-moodle-hierarchical.shThis script will:
- Remove old flat plugin directories (mod_, tool_, etc.)
- Preserve the new hierarchical structure and moodle-core
- Display the resulting directory structure
After migration:
- Rebuild your devcontainer or restart Docker containers to pick up the new bind mounts
- All xdebug path mappings will work automatically
Manual migration alternative:
# Clone new structure
./scripts/clone-repos.sh
# Remove old flat directories
./scripts/migrate-moodle-hierarchical.shTo add additional plugins, add them to the PLUGINS environment variable in AppHost.cs.
The Intelephense PHP language server is disabled by default to save approximately 337MB of memory. When working on Moodle/PHP code, you'll want to enable it for code completion, go-to-definition, and other IntelliSense features.
To enable Intelephense:
- Open Extensions panel (
Ctrl+Shift+X) - Search for "Intelephense"
- Click the gear icon and select "Enable (Workspace)"
- Reload VS Code window:
Ctrl+Shift+P→ "Reload Window"
To disable when done: Follow the same steps but select "Disable (Workspace)" to free up memory.
Two Moodle tasks are available for different debugging needs:
Normal Moodle Task (.env/moodle.env):
- Xdebug is off by default
- Use for general development and testing
- Faster startup and better performance
Moodle with Xdebug Task (.env/moodle-xdebug.env):
- Xdebug is enabled (
XDEBUG_MODE=debug) - Use when you need to set breakpoints and debug PHP code
- Requires VS Code debugger listener to be running
To debug Moodle PHP code:
- Select the "Moodle with Xdebug" task in VS Code
- Start the "Listen for Xdebug" debugger in VS Code (F5 or Run panel)
- Set breakpoints in your PHP code
- Access Moodle in the browser - breakpoints will be hit
Important: PHP on the Moodle container will pause execution if Xdebug is enabled but the VS Code debugger listener is not running. Always start the "Listen for Xdebug" debugger before accessing Moodle when using the Xdebug task.
Xdebug Filter: An xdebug filter (xdebug_filter.php) is automatically generated from your plugin configuration to limit the scope of code analyzed by Xdebug. This improves performance by only debugging your custom plugins and core Moodle paths, not third-party dependencies.
To make additional paths available for debugging, add the paths to Dockerfile.MoodleCustom,
add-moodle-mounts.sh, AppHost.cs, pre_configure.sh and launch.json.
The standard Moodle debugging level and display via the UI can be set under the normal Site
Administration, Development, menu. The install process for this container installs the plugin
tool_userdebug which allows site admins to easily toggle debug display via an icon added
to the header just to the left of the user avatar in the upper right corner of the screen.
This is the preferred method to enable display of debug messages inside of the browser.
The crucible-common-dotnet shared library is cloned into the /mnt/data/crucible/libraries directory. By default, APIs that use these libraries pull the published packages from NuGet. When developing or debugging these libraries, it is convenient to point the APIs to the local copy of the library. Developers can use the scripts/toggle-local-library.sh script to easily toggle between the default published NuGet packages and local Project References.
# Enable local library debugging (uses local EntityEvents source)
./scripts/toggle-local-library.sh on
# Disable local library debugging (uses NuGet packages)
./scripts/toggle-local-library.sh off
# Check current status
./scripts/toggle-local-library.sh status
# Toggle current state
./scripts/toggle-local-library.shA Directory.Build.props file is mounted to /mnt/data. This file defines a variable <UseLocalEntityEvents>false</UseLocalEntityEvents>. If you want to use the local version of the Crucible.Common.EntityEvents library, copy this file to /mnt/data/crucible and set <UseLocalEntityEvents>true</UseLocalEntityEvents>. This will tell MSBuild to use a local project reference instead of the NuGet package and this file will not get checked into git. The script automates this process for you.
This pattern should be extended to the other libraries in crucible-common-dotnet as necessary in the future.