This guide provides step-by-step instructions for manually testing the render command and verifying that it generates identical artifacts to the provision command.
The render command generates deployment artifacts without provisioning infrastructure. This allows you to:
- Preview what will be deployed before committing to infrastructure provisioning
- Generate artifacts for manual deployment
- Inspect and validate configuration before provisioning
- Compare artifacts between different configurations
Key Principle: The render command should generate identical artifacts to those created during provisioning, except for IP addresses (which come from real infrastructure during provision).
This manual test verifies:
- Artifact Generation: render command successfully creates all deployment artifacts
- Artifact Completeness: All 8 service templates are rendered (OpenTofu, Ansible, Docker Compose, Tracker, Prometheus, Grafana, Caddy, Backup)
- Artifact Equivalence: Artifacts match those created by provision command (except IP addresses)
- Dual Input Modes: Both
--env-nameand--env-filemodes work correctly - Idempotency: Multiple render calls produce consistent results
- LXD configured: Required for provision comparison (see LXD Setup)
- Test SSH keys: Use
fixtures/testing_rsaandfixtures/testing_rsa.pub - Clean workspace: No existing test environments
# Verify no existing test environment
cargo run -- list
# Check dependencies
cargo run --bin dependency-installer checkThis test compares render command output with actual provision command output.
# Generate configuration template
cargo run -- create template --provider lxd envs/render-test.json
# Customize the template
nano envs/render-test.jsonRequired customizations:
{
"environment": {
"name": "render-test"
},
"ssh_credentials": {
"private_key_path": "fixtures/testing_rsa",
"public_key_path": "fixtures/testing_rsa.pub",
"username": "torrust",
"port": 22
},
"provider": {
"provider": "lxd",
"profile_name": "torrust-profile-render-test"
}
}# Create environment (state: Created)
cargo run -- create environment --env-file envs/render-test.jsonExpected output:
✅ Environment 'render-test' created successfully!
State: Created
Verify environment state:
cargo run -- show render-testShould show state: Created
# Render artifacts with a test IP address to a preview directory
cargo run -- render --env-name render-test --instance-ip 192.168.1.100 --output-dir ./render-test-previewExpected output:
⏳ [1/3] Validating input parameters...
⏳ ✓ Done (took Xms)
⏳ [2/3] Loading configuration...
⏳ ✓ Done (took Xms)
⏳ [3/3] Generating deployment artifacts...
⏳ ✓ Done (took Xms)
✅
Deployment artifacts generated successfully!
Source: Environment: render-test
Target IP: 192.168.1.100
Output: ./render-test-preview
Next steps:
- Review artifacts in the output directory
- Use 'provision' command to deploy infrastructure
- Or use artifacts manually with your deployment tools
Save the rendered artifacts location:
RENDER_PREVIEW_DIR="render-test-preview"# List generated artifacts
ls -la $RENDER_PREVIEW_DIR/
# Should contain:
# - tofu/ - OpenTofu infrastructure code
# - ansible/ - Ansible playbooks and inventory
# - docker-compose/ - Docker Compose configuration
# - tracker/ - Tracker configuration
# - prometheus/ - Prometheus configuration
# - grafana/ - Grafana provisioning
# - caddy/ - Caddy reverse proxy config (if HTTPS enabled)
# - backup/ - Backup scripts (if enabled)Check OpenTofu configuration:
cat $RENDER_PREVIEW_DIR/tofu/main.tf
# Should contain LXD VM configurationCheck Ansible inventory:
cat $RENDER_PREVIEW_DIR/ansible/inventory.yml
# Should contain: ansible_host: 192.168.1.100Check Docker Compose:
cat $RENDER_PREVIEW_DIR/docker-compose/docker-compose.yml
# Should contain service definitions# Provision infrastructure - this will create a real VM
cargo run -- provision render-testExpected output:
✅ Environment 'render-test' provisioned successfully!
Instance IP: <ACTUAL_IP>
SSH Port: 22
Instance Name: render-test-<ID>
State: Provisioned
Save the actual IP for comparison:
# Get actual IP from show command
ACTUAL_IP=$(cargo run -- show render-test | grep "IP Address" | awk '{print $3}')
echo "Actual IP: $ACTUAL_IP"Note: Provisioning will also generate artifacts in build/render-test/ with the actual IP.
# After provision completes, compare render preview with provision output
# Preview directory: render-test-preview (from render command)
# Provision directory: build/render-test/ (from provision command)
diff -r $RENDER_PREVIEW_DIR build/render-test/Expected differences:
-
Ansible inventory - IP addresses:
# Rendered version ansible_host: 192.168.1.100 # Test IP # Provisioned version ansible_host: 10.x.x.x # Actual VM IP
-
No other differences - All other files should be identical
Detailed comparison:
# Compare OpenTofu configurations (should be identical)
diff $RENDER_PREVIEW_DIR/tofu/main.tf build/render-test/tofu/main.tf
# Compare Docker Compose (should be identical)
diff $RENDER_PREVIEW_DIR/docker-compose/docker-compose.yml \
build/render-test/docker-compose/docker-compose.yml
# Compare Ansible inventory (only IP should differ)
diff $RENDER_PREVIEW_DIR/ansible/inventory.yml \
build/render-test/ansible/inventory.yml# Try to render again to the same output directory (should fail without --force)
cargo run -- render --env-name render-test --instance-ip 192.168.1.100 --output-dir ./render-test-preview
# Should fail with: "Output directory already exists"
# With --force flag, should succeed
cargo run -- render --env-name render-test --instance-ip 192.168.1.100 --output-dir ./render-test-preview --force
# Should succeed without errors
# Artifacts should remain unchanged# Destroy the infrastructure
cargo run -- destroy render-test
# Purge the environment data
cargo run -- purge render-test --force
# Remove preview artifacts
rm -rf $RENDER_PREVIEW_DIRThis test verifies the --env-file mode which generates artifacts directly from a config file.
# Create config file (don't create environment)
cargo run -- create template --provider lxd envs/render-direct.json
# Customize configuration
nano envs/render-direct.jsonSet environment name:
{
"environment": {
"name": "render-direct"
}
}# Render directly from config file (no environment creation)
cargo run -- render --env-file envs/render-direct.json --instance-ip 192.168.1.200 --output-dir ./render-direct-artifactsExpected output:
✅
Deployment artifacts generated successfully!
Source: Config file: envs/render-direct.json
Target IP: 192.168.1.200
Output: ./render-direct-artifacts
# Check artifacts were generated
ls -la render-direct-artifacts/
# Verify IP in Ansible inventory
grep "ansible_host" render-direct-artifacts/ansible/inventory.yml
# Should show: ansible_host: 192.168.1.200# List environments - render-direct should NOT exist
cargo run -- list
# Should NOT show 'render-direct' environment# Remove artifacts
rm -rf render-direct-artifactsTest 1 (Environment Name Mode):
- ✅ Environment created successfully
- ✅ Render command succeeds with valid IP
- ✅ All 8 service artifacts generated in
build/render-test/ - ✅ Provision command succeeds
- ✅ Rendered artifacts match provisioned artifacts (except IP addresses)
- ✅ Only Ansible inventory shows IP difference (192.168.1.100 vs actual IP)
- ✅ Idempotent - multiple renders succeed without errors
Test 2 (Config File Mode):
- ✅ Render from config file succeeds without creating environment
- ✅ All artifacts generated in
./render-direct-artifacts/ - ✅ IP address correctly set in Ansible inventory
- ✅ Environment NOT created in data directory
Cause: Environment doesn't exist or is in wrong state
Solution:
# Check environment exists and is in Created state
cargo run -- show <env-name>
# If not in Created state, cannot renderCause: Target output directory from previous render
Solution:
# Remove old directory
rm -rf ./my-output-dir
# Or use --force to overwrite
cargo run -- render --env-name test --instance-ip 192.168.1.100 --output-dir ./my-output-dir --forceCause: Invalid IP format provided
Solution:
# Use valid IPv4 or IPv6 format
cargo run -- render --env-name test --instance-ip 192.168.1.100 --output-dir ./test-preview # Valid
cargo run -- render --env-name test --instance-ip invalid-ip --output-dir ./test-preview # InvalidCause: Environment already provisioned (not in Created state)
Expected behavior: Render command should inform you where existing artifacts are located
Solution: This is informational, not an error. The artifacts are already in build/<env-name>/ from the provision command.
After completing both tests, verify:
-
tofu/directory contains infrastructure code -
ansible/directory contains playbooks and inventory -
docker-compose/directory contains service definitions -
tracker/directory contains tracker.toml -
prometheus/directory contains prometheus.yml -
grafana/directory contains provisioning configs -
caddy/directory exists (if HTTPS enabled) -
backup/directory exists (if backup enabled)
- OpenTofu configs identical between render and provision
- Docker Compose files identical between render and provision
- Ansible playbooks identical between render and provision
- Only Ansible inventory IP differs (test IP vs actual IP)
- Tracker configuration identical
- Prometheus configuration identical
- Grafana provisioning identical
- Render succeeds with
--env-namefor Created environments - Render succeeds with
--env-filewithout creating environment - Render is idempotent (can be called multiple times)
- Render provides clear success messages
- Render validates IP address format
- Render fails gracefully for non-existent environments
- Render fails gracefully for missing config files
- Complete Manual Testing Guide - Full deployment workflow testing
- Render Command Reference - Command documentation
- E2E Testing Overview - Automated E2E test documentation
- Troubleshooting Guide - Common issues and solutions
-
IP Addresses: The render command requires an IP address because:
- In Created state, infrastructure doesn't exist yet
- With config file, no infrastructure is ever created
- IP is needed for Ansible inventory generation
-
Output Directory: Artifacts are generated in the user-specified output directory (via
--output-dirflag). This separates preview artifacts from deployment artifacts inbuild/<env-name>/. -
State Independence: Render is a read-only operation - it never changes environment state
-
Artifact Parity: This test is critical for ensuring the render command is a true "preview" of what provision will generate