✅ Accepted
2025-12-10
When designing the E2E testing architecture for deployment workflow tests, we initially planned to create multiple Docker images representing different deployment phases:
provisioned-instance- Post-provision state (base system ready)configured-instance- Post-configure state (dependencies installed)released-instance- Post-release state (applications deployed)running-instance- Post-run state (services started)
This multi-image approach would theoretically allow:
- Isolated phase testing: Test individual commands (configure, release, run, test) independently
- Parallel test execution: Run E2E tests for different commands in parallel
- Clear phase boundaries: Each image captures the exact state after a specific deployment phase
However, implementing and maintaining this architecture presented significant challenges:
- High Maintenance Overhead: Every code change affecting any deployment phase requires updating multiple Docker images
- Image Synchronization: Keeping all phase images in sync with code changes is error-prone and time-consuming
- Build Time: Building multiple Docker images sequentially would be slower than running commands sequentially in a single container
- Parallel Execution Overhead: Even with parallel tests, the Docker build and startup time for multiple images outweighs the benefits
- Complexity: Managing multiple Dockerfiles, build dependencies, and test orchestration adds significant complexity
- Duplication: Much of the image content would be duplicated across phases (base system, users, SSH setup)
The fundamental trade-off is between test isolation/parallelism (multiple images) versus maintainability/simplicity (single image).
We will use a single Docker image (provisioned-instance) representing the pre-provisioned instance state, and run all deployment commands sequentially within that container during E2E tests.
Single Image Approach:
docker/provisioned-instance/
├── Dockerfile # Ubuntu 24.04 LTS + SSH + torrust user
├── supervisord.conf # Process management
├── entrypoint.sh # Container initialization
└── README.md # Documentation
Sequential Command Execution:
// E2E test workflow (simplified)
async fn run_deployment_workflow_tests() -> Result<()> {
// 1. Start single container (provisioned state)
let container = start_provisioned_container().await?;
// 2. Run commands sequentially
run_create_command()?;
run_register_command(container.ip())?;
run_configure_command()?; // Modifies container state
run_release_command()?; // Modifies container state
run_run_command()?; // Modifies container state
run_test_command()?; // Validates container state
// 3. Cleanup
container.stop().await?;
Ok(())
}✅ Benefits:
- Low Maintenance: Single Dockerfile to maintain - changes propagate automatically
- Simpler Architecture: Clear, understandable test flow
- Faster Overall: Sequential execution in one container is faster than building/starting multiple images
- Easy Debugging: Single container lifecycle to understand and inspect
- Code Synchronization: Image changes automatically reflect code changes via Ansible playbooks
❌ Trade-offs:
- No Command Isolation: Cannot test individual commands independently (must run full sequence)
- No Test Parallelism: Cannot run E2E tests for different commands in parallel
- State Accumulation: Later commands see state from earlier commands (intentional - tests real workflow)
- Longer Test Runs: If one command fails, must re-run entire sequence
- Reduced Complexity: Single Dockerfile, single container, single test flow
- Better Maintainability: Code changes automatically tested via playbooks without image rebuilds
- Realistic Testing: Sequential execution matches real deployment workflow exactly
- Faster Iteration: No need to rebuild multiple images during development
- Lower CI Resources: Single container uses fewer resources than multiple containers
- Simplified Debugging:
--keepflag allows inspection of final container state with all commands applied
- Test Coupling: Commands cannot be tested in isolation - must test full workflow
- Longer Feedback: Must run entire sequence to test later commands
- No Parallel Speedup: Cannot leverage parallel test execution for E2E workflow tests
The negative consequences are mitigated by:
- Unit Tests: Individual command logic is tested in isolation via unit tests
- Integration Tests: Command interfaces are tested without full E2E overhead
- Fast Execution: Sequential execution in Docker is still fast (~48 seconds total)
- Split Test Suites: Infrastructure tests run separately, allowing some parallelism at the suite level
Approach: Build separate Docker images for each deployment phase (provisioned, configured, released, running).
Pros:
- Command isolation - test individual commands independently
- Parallel test execution possible
- Clear phase boundaries
Cons:
- High maintenance overhead - must update multiple images for code changes
- Slower build time - building 4 images takes longer than running 4 commands
- Complex orchestration - managing image dependencies and build order
- Image synchronization issues - keeping images in sync with code
- Higher CI resource usage
Rejected Because: Maintenance overhead outweighs benefits. Build time for multiple images exceeds sequential execution time.
Approach: Use Docker Compose to orchestrate multiple containers representing different phases.
Pros:
- Service isolation
- Declarative configuration
- Can leverage Docker Compose features
Cons:
- Even higher complexity than multi-image
- Still requires building/maintaining multiple images
- Orchestration overhead
- Harder to debug
Rejected Because: Adds orchestration complexity without solving the fundamental maintenance problem.
Approach: Start with one image, create container snapshots after each command, test from snapshots.
Pros:
- Single base image
- Can jump to any phase via snapshot
- Some test isolation
Cons:
- Snapshot management complexity
- Storage overhead for snapshots
- Non-standard testing approach
- Still requires careful state management
Rejected Because: Complexity doesn't justify the limited benefits. Snapshots add non-standard workflow.
- Docker Testing Evolution - Evolution from Docker rejection to hybrid approach for E2E testing
- E2E Test Split Architecture - Split between infrastructure and deployment workflow tests
- E2E Testing Guide - Docker Architecture
- Provisioned Instance Documentation
- GitHub Actions E2E Deployment Workflow:
.github/workflows/test-e2e-deployment.yml - E2E Deployment Workflow Tests:
src/bin/e2e_deployment_workflow_tests.rs