torrust-tracker-deployer 0.1.0

Torrust Tracker Deployer - Deployment Infrastructure with Ansible and OpenTofu
Documentation
# Decision: Single Docker Image for Sequential E2E Command Testing

## Status

✅ Accepted

## Date

2025-12-10

## Context

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:

1. **High Maintenance Overhead**: Every code change affecting any deployment phase requires updating multiple Docker images
2. **Image Synchronization**: Keeping all phase images in sync with code changes is error-prone and time-consuming
3. **Build Time**: Building multiple Docker images sequentially would be slower than running commands sequentially in a single container
4. **Parallel Execution Overhead**: Even with parallel tests, the Docker build and startup time for multiple images outweighs the benefits
5. **Complexity**: Managing multiple Dockerfiles, build dependencies, and test orchestration adds significant complexity
6. **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).

## Decision

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.

### Implementation Details

**Single Image Approach**:

```text
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**:

```rust
// 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(())
}
```

### Trade-offs Accepted

**✅ 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

## Consequences

### Positive

1. **Reduced Complexity**: Single Dockerfile, single container, single test flow
2. **Better Maintainability**: Code changes automatically tested via playbooks without image rebuilds
3. **Realistic Testing**: Sequential execution matches real deployment workflow exactly
4. **Faster Iteration**: No need to rebuild multiple images during development
5. **Lower CI Resources**: Single container uses fewer resources than multiple containers
6. **Simplified Debugging**: `--keep` flag allows inspection of final container state with all commands applied

### Negative

1. **Test Coupling**: Commands cannot be tested in isolation - must test full workflow
2. **Longer Feedback**: Must run entire sequence to test later commands
3. **No Parallel Speedup**: Cannot leverage parallel test execution for E2E workflow tests

### Risk Mitigation

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

## Alternatives Considered

### Alternative 1: Multi-Image Phase Architecture (Original Plan)

**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.

### Alternative 2: Docker Compose Multi-Service Setup

**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.

### Alternative 3: Container Snapshots Between Commands

**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.

## Related Decisions

- [Docker Testing Evolution]./docker-testing-evolution.md - Evolution from Docker rejection to hybrid approach for E2E testing
- [E2E Test Split Architecture]../e2e-testing.md#architecture - Split between infrastructure and deployment workflow tests

## References

- [E2E Testing Guide - Docker Architecture]../e2e-testing.md#docker-architecture-for-e2e-testing
- [Provisioned Instance Documentation]../../docker/provisioned-instance/README.md
- GitHub Actions E2E Deployment Workflow: `.github/workflows/test-e2e-deployment.yml`
- E2E Deployment Workflow Tests: `src/bin/e2e_deployment_workflow_tests.rs`