Run a complete local end-to-end test using the LXD provider to verify a feature, fix, or change works as expected from the user's perspective. Covers creating an environment config, provisioning a VM, deploying the tracker, verifying it works, and cleaning up. Use when manually testing changes, validating a new feature locally, running a full deployment smoke test, or checking that the deployer works end-to-end. Triggers on "local e2e", "manual e2e test", "test my changes locally", "run deployer end-to-end", "smoke test", "test on LXD", "verify feature works", or "full deployment test".
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Run a complete local end-to-end test using the LXD provider to verify a feature, fix, or change works as expected from the user's perspective. Covers creating an environment config, provisioning a VM, deploying the tracker, verifying it works, and cleaning up. Use when manually testing changes, validating a new feature locally, running a full deployment smoke test, or checking that the deployer works end-to-end. Triggers on "local e2e", "manual e2e test", "test my changes locally", "run deployer end-to-end", "smoke test", "test on LXD", "verify feature works", or "full deployment test".
metadata
{"author":"torrust","version":"1.0"}
Run Local E2E Test (LXD Provider)
This skill walks you through a complete manual end-to-end test of the deployer using a local LXD VM. Use it whenever you want to verify that a feature, fix, or change works as an end-user would experience it.
When to Use This Skill
After implementing a new feature โ verify the full workflow works
After fixing a bug โ confirm the fix in a real deployment
Before opening a PR โ smoke-test your changes locally
Investigating a user-reported issue โ reproduce it end-to-end
Prerequisites
# Verify all required tools are installed
cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- check
# Install missing tools (LXD, OpenTofu, Ansible, Docker)
cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install
Note: cargo run --bin dependency-installer does not work from the workspace root because the binary lives in a sub-package. Always use .
-p torrust-tracker-deployer-dependency-installer
Complete Workflow
Step 1: Create the Environment Config
# Generate a fresh config template
cargo run -- create template --provider lxd envs/my-local-test.json
The generated template contains three REPLACE_WITH_* placeholders that must be filled before proceeding:
Placeholder
Example value
REPLACE_WITH_ENVIRONMENT_NAME
my-local-test
REPLACE_WITH_SSH_PRIVATE_KEY_ABSOLUTE_PATH
/absolute/path/to/repo/fixtures/testing_rsa
REPLACE_WITH_SSH_PUBLIC_KEY_ABSOLUTE_PATH
/absolute/path/to/repo/fixtures/testing_rsa.pub
REPLACE_WITH_LXD_PROFILE_NAME
torrust-profile-my-local-test
Use absolute paths for SSH keys. The fixtures/testing_rsa key pair is ready to use for local testing. The profile name must be unique per environment.
Step 2: Create Environment
cargo run -- create environment --env-file envs/my-local-test.json
Creates data/my-local-test/ and build/my-local-test/.
Step 3: Provision VM (~30โ90s)
cargo run -- provision my-local-test --log-output file-and-stderr
Creates the LXD profile and VM, waits for SSH and cloud-init to complete. The command prints the instance IP and an SSH connection string on success.
# Re-query the IP at any point after provisioning# Note: pipe 2>/dev/null to suppress cargo build output before jqexport INSTANCE_IP=$(cargo run -- show my-local-test --output-format json 2>/dev/null | jq -r '.infrastructure.instance_ip')
echo"VM IP: $INSTANCE_IP"
Step 4: Configure Software (~40โ60s)
cargo run -- configure my-local-test
Installs Docker and Docker Compose on the VM.
# Get IP and verify Docker is installed on the VMexport INSTANCE_IP=$(cargo run -- show my-local-test --output-format json 2>/dev/null | jq -r '.infrastructure.instance_ip')
ssh -i fixtures/testing_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
torrust@$INSTANCE_IP"docker --version && docker compose version"
Step 5: Release (~7โ10s)
cargo run -- release my-local-test
Pulls the tracker Docker image and prepares the runtime environment.
Step 6: Run Tracker (~10โ15s)
cargo run -- run my-local-test
Starts the tracker container and waits for health checks. On success the command prints all service URLs (UDP, HTTP tracker, API, Grafana) โ no need to run show just to find them.
Step 7: Test (~5โ15s)
cargo run -- test my-local-test
Runs automated health checks against the running deployment (HTTP tracker, HTTP API, health endpoints). A good first gate before doing any manual verification.
Step 8: Verify (manual)
What to verify depends on what you are testing. Refer to the relevant guide in docs/e2e-testing/manual/:
# Tear down the VM and infrastructure
cargo run -- destroy my-local-test
# Remove local data/build directories and registry entry
cargo run -- purge my-local-test --force
destroy removes the infrastructure (LXD VM, profile, OpenTofu state) but keeps the local environment data. Its output already reminds you to run purge. purge removes the remaining data/ and build/ directories so the environment name can be reused. Use --force to skip the interactive confirmation prompt.
# Verify cleanup: show exits with code 1 once the environment is purged
cargo run -- show my-local-test; echo"exit: $?"
Quick Reference: Show Command
Use the show command instead of parsing internal state files:
# Human-readable summary (includes IP, SSH command, service URLs)
cargo run -- show <env-name>
# Machine-readable JSON (for scripting)
cargo run -- show <env-name> --output-format json
# Extract IP for scripting (available once provisioned)# Note: 2>/dev/null suppresses cargo build output so jq only receives JSONexport INSTANCE_IP=$(cargo run -- show <env-name> --output-format json 2>/dev/null | jq -r '.infrastructure.instance_ip')
# Get current state name
cargo run -- show <env-name> --output-format json | jq -r '.state'
Recovery After Interruption
If a command is interrupted mid-run:
# Check current state
cargo run -- show my-local-test --output-format json 2>/dev/null | jq -r '.state'
Provisioning (interrupted): destroy and retry from Step 3