torrust-tracker-deployer 0.1.0

Torrust Tracker Deployer - Deployment Infrastructure with Ansible and OpenTofu
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
# `provision` - Provision VM Infrastructure

Provision virtual machine infrastructure for a deployment environment.

## Purpose

Creates and configures VM infrastructure using OpenTofu (Terraform). This command takes an environment from the "Created" state to the "Provisioned" state with running VM instances.

The provision command works with all supported providers:

- **LXD** - Creates local VMs for development and testing
- **Hetzner Cloud** - Creates cloud servers for production deployments

## Command Syntax

```bash
torrust-tracker-deployer provision <ENVIRONMENT>
```

## Arguments

- `<ENVIRONMENT>` (required) - Name of the environment to provision

## Verbosity Levels

Control the amount of progress detail displayed during provisioning with the global `-v` flag. This helps you see what's happening under the hood when you need more visibility.

### Available Levels

| Level           | Flag      | Shows                                      | Use Case                                    |
| --------------- | --------- | ------------------------------------------ | ------------------------------------------- |
| **Normal**      | (default) | Essential progress and results             | Regular usage, clean output                 |
| **Verbose**     | `-v`      | + Detailed progress (9 provisioning steps) | Understanding the provisioning workflow     |
| **VeryVerbose** | `-vv`     | + Context details (paths, status, retries) | Troubleshooting common issues               |
| **Debug**       | `-vvv`    | + Technical details (commands, parameters) | Deep troubleshooting, development debugging |

**Important**: Verbosity controls **only** progress messages. For internal diagnostic logs, use the `RUST_LOG` environment variable (see [Logging Guide](../logging.md)).

### Normal Level (Default)

Shows essential progress with minimal output:

```bash
torrust-tracker-deployer provision my-env
```

**Output**:

```text
⏳ [1/3] Validating environment...
⏳   ✓ Environment name validated: my-env (took 0ms)
⏳ [2/3] Creating command handler...
⏳   ✓ Done (took 0ms)
⏳ [3/3] Provisioning infrastructure...
⏳   ✓ Infrastructure provisioned (took 26.5s)
✅ Environment 'my-env' provisioned successfully

Instance Connection Details:
  IP Address:        10.140.190.42
  SSH Port:          22
  ...
```

### Verbose Level (`-v`)

Shows the 9 internal provisioning steps:

```bash
torrust-tracker-deployer provision my-env -v
```

**Output**:

```text
⏳ [1/3] Validating environment...
⏳   ✓ Environment name validated: my-env (took 0ms)
⏳ [2/3] Creating command handler...
⏳   ✓ Done (took 0ms)
⏳ [3/3] Provisioning infrastructure...
📋   [Step 1/9] Rendering OpenTofu templates...
📋   [Step 2/9] Initializing OpenTofu...
📋   [Step 3/9] Validating infrastructure configuration...
📋   [Step 4/9] Planning infrastructure changes...
📋   [Step 5/9] Applying infrastructure changes...
📋   [Step 6/9] Retrieving instance information...
📋   [Step 7/9] Rendering Ansible templates...
📋   [Step 8/9] Waiting for SSH connectivity...
📋   [Step 9/9] Waiting for cloud-init completion...
⏳   ✓ Infrastructure provisioned (took 26.5s)
✅ Environment 'my-env' provisioned successfully
```

**When to use**: Understanding the provisioning workflow, seeing which step is taking time, or confirming the command is making progress.

### VeryVerbose Level (`-vv`)

Adds contextual details like paths, validation results, and retry attempts:

```bash
torrust-tracker-deployer provision my-env -vv
```

**Output**:

```text
⏳ [3/3] Provisioning infrastructure...
📋   [Step 1/9] Rendering OpenTofu templates...
📋      → Generated OpenTofu configuration files
📋   [Step 2/9] Initializing OpenTofu...
📋      → Initialized OpenTofu backend
📋   [Step 3/9] Validating infrastructure configuration...
📋      → Configuration is valid ✓
📋   [Step 4/9] Planning infrastructure changes...
📋      → Plan: 2 to add, 0 to change, 0 to destroy.
📋   [Step 5/9] Applying infrastructure changes...
📋      → Infrastructure resources created successfully
📋   [Step 6/9] Retrieving instance information...
📋      → Instance IP: 10.140.190.42
📋   [Step 7/9] Rendering Ansible templates...
📋      → Template directory: ./build/my-env/ansible
📋      → Generated inventory and playbooks
📋   [Step 8/9] Waiting for SSH connectivity...
📋      → Testing connection to 10.140.190.42:22
📋      → SSH connection established ✓
📋   [Step 9/9] Waiting for cloud-init completion...
📋      → Cloud-init status: done ✓
⏳   ✓ Infrastructure provisioned (took 26.5s)
```

**When to use**: Troubleshooting SSH connectivity issues, verifying file locations, understanding why a step failed, or monitoring retry attempts.

### Debug Level (`-vvv`)

Shows technical implementation details including commands executed:

```bash
torrust-tracker-deployer provision my-env -vvv
```

**Output**:

```text
⏳ [3/3] Provisioning infrastructure...
📋   [Step 1/9] Rendering OpenTofu templates...
🔍      → Template generator: torrust_tracker_deployer_lib::infrastructure::templating::tofu::...
📋      → Generated OpenTofu configuration files
📋   [Step 2/9] Initializing OpenTofu...
🔍      → Working directory: ./build/my-env/tofu/lxd
🔍      → Executing: tofu init
🔍      → Command completed successfully
📋      → Initialized OpenTofu backend
📋   [Step 3/9] Validating infrastructure configuration...
🔍      → Working directory: ./build/my-env/tofu/lxd
🔍      → Executing: tofu validate
🔍      → Validation output: Success! The configuration is valid.
📋      → Configuration is valid ✓
📋   [Step 4/9] Planning infrastructure changes...
🔍      → Working directory: ./build/my-env/tofu/lxd
🔍      → Executing: tofu plan -var-file=variables.tfvars
📋      → Plan: 2 to add, 0 to change, 0 to destroy.
📋   [Step 5/9] Applying infrastructure changes...
🔍      → Working directory: ./build/my-env/tofu/lxd
🔍      → Executing: tofu apply -var-file=variables.tfvars -auto-approve
📋      → Infrastructure resources created successfully
...
```

**When to use**: Deep debugging, understanding exactly what commands are executed, verifying working directories, or reporting issues with detailed context.

**Symbol Legend**:

- ⏳ = Major progress milestone (all levels)
- ✅ = Success message (all levels)
- 📋 = Detailed progress (Verbose `-v` and above)
- 🔍 = Technical details (Debug `-vvv` only)

### Combining with Other Flags

Verbosity works with all other flags:

```bash
# Verbose output with JSON result format
torrust-tracker-deployer provision my-env -v --output-format json

# Debug verbosity with file-only logging
torrust-tracker-deployer provision my-env -vvv --log-output file-only

# Very verbose in CI environment
LOG_OUTPUT=file-only torrust-tracker-deployer provision my-env -vv
```

## Output Formats

The `provision` command supports two output formats for command results:

- **Text** (default) - Human-readable formatted output
- **JSON** - Machine-readable JSON for automation

Use the global `--output-format` flag to control the format.

### Text Output (Default)

The default output format provides human-readable information with visual formatting:

```bash
torrust-tracker-deployer provision full-stack-docs
```

**Output**:

```text
⏳ [1/3] Validating environment...
⏳   ✓ Environment name validated: full-stack-docs (took 0ms)
⏳ [2/3] Creating command handler...
⏳   ✓ Done (took 0ms)
⏳ [3/3] Provisioning infrastructure...
⏳   ✓ Infrastructure provisioned (took 26.5s)
✅ Environment 'full-stack-docs' provisioned successfully


Instance Connection Details:
  IP Address:        10.140.190.211
  SSH Port:          22
  SSH Private Key:   /home/josecelano/Documents/git/committer/me/github/torrust/torrust-tracker-deployer-agent-01/fixtures/testing_rsa
  SSH Username:      torrust

Connect using:
  ssh -i /home/josecelano/Documents/git/committer/me/github/torrust/torrust-tracker-deployer-agent-01/fixtures/testing_rsa torrust@10.140.190.211 -p 22

⚠️  DNS Setup Required:
  Your configuration uses custom domains. Remember to update your DNS records
  to point your domains to the server IP: 10.140.190.211

  Configured domains:
    - tracker1.example.com
    - tracker2.example.com
    - api.example.com
    - grafana.example.com
    - health.example.com
```

**Features**:

- Progress indicators (✓)
- Numbered list format
- Clear section organization
- Color-coded status messages

### JSON Output

Use `--output-format json` for machine-readable output ideal for automation, scripts, and programmatic processing:

```bash
torrust-tracker-deployer provision my-environment --output-format json
```

**Output**:

```json
{
  "environment_name": "my-environment",
  "instance_name": "torrust-tracker-vm-my-environment",
  "instance_ip": "10.140.190.42",
  "ssh_private_key_path": "/home/user/.ssh/id_rsa",
  "ssh_public_key_path": "/home/user/.ssh/id_rsa.pub",
  "ssh_username": "torrust",
  "ssh_port": 22,
  "provider": "lxd",
  "domains": [],
  "provisioned_at": "2026-02-16T13:38:02.446056727Z"
}
```

**Features**:

- Valid, parseable JSON
- Pretty-printed for readability
- ISO 8601 timestamps
- Consistent field ordering
- Ready for immediate SSH automation

#### JSON Schema

| Field                  | Type     | Description                        | Example                            |
| ---------------------- | -------- | ---------------------------------- | ---------------------------------- |
| `environment_name`     | string   | Name of the environment            | `"production"`                     |
| `instance_name`        | string   | Full VM instance name              | `"torrust-tracker-vm-production"`  |
| `instance_ip`          | string   | IP address of provisioned VM       | `"10.140.190.42"`                  |
| `ssh_private_key_path` | string   | Path to SSH private key            | `"/home/user/.ssh/id_rsa"`         |
| `ssh_public_key_path`  | string   | Path to SSH public key             | `"/home/user/.ssh/id_rsa.pub"`     |
| `ssh_username`         | string   | SSH username for VM access         | `"torrust"`                        |
| `ssh_port`             | number   | SSH port number                    | `22`                               |
| `provider`             | string   | Provider used ("lxd" or "hetzner") | `"lxd"`                            |
| `domains`              | string[] | Configured domains (HTTPS only)    | `["tracker.example.com"]`          |
| `provisioned_at`       | string   | ISO 8601 timestamp of provisioning | `"2026-02-16T13:38:02.446056727Z"` |

#### Short Form

Use the `-o` alias for shorter commands:

```bash
torrust-tracker-deployer provision my-environment -o json
```

### Automation Examples

#### Extract IP Address for SSH Automation

```bash
#!/bin/bash

# Provision environment and capture JSON output
JSON_OUTPUT=$(torrust-tracker-deployer provision my-environment \
  --output-format json \
  --log-output file-only)

# Extract instance IP and SSH credentials using jq
INSTANCE_IP=$(echo "$JSON_OUTPUT" | jq -r '.instance_ip')
SSH_KEY=$(echo "$JSON_OUTPUT" | jq -r '.ssh_private_key_path')
SSH_USER=$(echo "$JSON_OUTPUT" | jq -r '.ssh_username')

echo "VM provisioned at: $INSTANCE_IP"

# Connect to the instance
ssh -i "$SSH_KEY" "$SSH_USER@$INSTANCE_IP"
```

#### CI/CD Pipeline Integration (GitHub Actions)

```yaml
name: Deploy Tracker

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Provision Infrastructure
        id: provision
        run: |
          OUTPUT=$(torrust-tracker-deployer provision ${{ env.ENVIRONMENT_NAME }} \
            --output-format json \
            --log-output file-only)

          echo "ip=$(echo $OUTPUT | jq -r '.instance_ip')" >> $GITHUB_OUTPUT
          echo "ssh_key=$(echo $OUTPUT | jq -r '.ssh_private_key_path')" >> $GITHUB_OUTPUT
          echo "ssh_user=$(echo $OUTPUT | jq -r '.ssh_username')" >> $GITHUB_OUTPUT

      - name: Configure DNS
        if: ${{ steps.provision.outputs.ip != '' }}
        run: |
          # Update DNS A record with provisioned IP
          curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
            -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
            -H "Content-Type: application/json" \
            --data '{"type":"A","name":"tracker","content":"${{ steps.provision.outputs.ip }}"}'

      - name: Deploy Application
        run: |
          ssh -i "${{ steps.provision.outputs.ssh_key }}" \
              "${{ steps.provision.outputs.ssh_user }}@${{ steps.provision.outputs.ip }}" \
              'cd /opt/torrust && docker compose up -d'
```

#### Python Script for Multi-Region Deployment

```python
import json
import subprocess
from typing import Dict, List

def provision_environment(env_name: str) -> Dict[str, any]:
    """Provision an environment and return provisioning details."""
    result = subprocess.run(
        [
            "torrust-tracker-deployer",
            "provision",
            env_name,
            "--output-format", "json",
            "--log-output", "file-only"
        ],
        capture_output=True,
        text=True,
        check=True
    )
    return json.loads(result.stdout)

def deploy_multi_region(regions: List[str]):
    """Deploy tracker to multiple regions in parallel."""
    deployments = []

    for region in regions:
        env_name = f"tracker-{region}"
        print(f"Provisioning {env_name}...")

        details = provision_environment(env_name)
        deployments.append({
            "region": region,
            "ip": details["instance_ip"],
            "ssh_user": details["ssh_username"],
            "ssh_key": details["ssh_private_key_path"],
            "provisioned_at": details["provisioned_at"]
        })

    # Save deployment manifest
    with open("deployments.json", "w") as f:
        json.dump(deployments, f, indent=2)

    print(f"\nDeployed to {len(deployments)} regions")
    for deployment in deployments:
        print(f"  {deployment['region']}: {deployment['ip']}")

# Deploy to multiple regions
deploy_multi_region(["us-east", "eu-west", "ap-southeast"])
```

#### Terraform/OpenTofu Integration

```hcl
# external-data.tf
# Use provisioned VM in downstream Terraform configuration

data "external" "provision_vm" {
  program = ["bash", "-c", <<-EOF
    torrust-tracker-deployer provision my-environment \
      --output-format json \
      --log-output file-only
  EOF
  ]
}

resource "cloudflare_record" "tracker_a_record" {
  zone_id = var.cloudflare_zone_id
  name    = "tracker"
  value   = data.external.provision_vm.result.instance_ip
  type    = "A"
  ttl     = 300
}

output "vm_ip" {
  value = data.external.provision_vm.result.instance_ip
}

output "ssh_command" {
  value = "ssh -i ${data.external.provision_vm.result.ssh_private_key_path} ${data.external.provision_vm.result.ssh_username}@${data.external.provision_vm.result.instance_ip}"
}
```

#### Extract Domains for HTTPS Configuration

```bash
#!/bin/bash

# Provision HTTPS environment
JSON_OUTPUT=$(torrust-tracker-deployer provision https-tracker \
  --output-format json \
  --log-output file-only)

# Extract domains array
DOMAINS=$(echo "$JSON_OUTPUT" | jq -r '.domains[]')

if [ -z "$DOMAINS" ]; then
  echo "No domains configured - HTTP-only deployment"
else
  echo "Configured domains:"
  echo "$DOMAINS"

  # Generate SSL certificates for each domain
  for domain in $DOMAINS; do
    echo "Requesting certificate for $domain..."
    certbot certonly --standalone -d "$domain" --non-interactive --agree-tos
  done
fi
```

## Prerequisites

1. **Environment created** - Must run `create environment` first
2. **Provider-specific requirements**:
   - **LXD**: Local LXD installation configured
   - **Hetzner**: Valid API token in environment configuration
3. **OpenTofu installed** - OpenTofu CLI available in PATH
4. **SSH keys** - SSH key pair referenced in environment configuration

📖 **See [Provider Guides](../providers/README.md)** for provider-specific setup.

## State Transition

```text
[Created] --provision--> [Provisioned]
```

## What Happens

When you provision an environment:

1. **Renders OpenTofu templates** - Generates provider-specific infrastructure-as-code files
2. **Initializes OpenTofu** - Sets up backend and providers (`tofu init`)
3. **Creates execution plan** - Validates configuration (`tofu plan`)
4. **Applies infrastructure** - Creates VM resources (`tofu apply`)
5. **Retrieves instance info** - Gets IP address and instance details
6. **Renders Ansible templates** - Generates configuration management files
7. **Waits for SSH** - Verifies network connectivity
8. **Waits for cloud-init** - Ensures VM initialization is complete
9. **Updates environment state** - Transitions to "Provisioned"

## Examples

### Basic provisioning (Text Output)

```bash
# Provision the environment with human-readable output
torrust-tracker-deployer provision my-environment

# Output:
# ✓ Rendering OpenTofu templates...
# ✓ Initializing infrastructure...
# ✓ Planning infrastructure changes...
# ✓ Applying infrastructure...
# ✓ Retrieving instance information...
# ✓ Instance IP: 10.140.190.42
# ✓ Rendering Ansible templates...
# ✓ Waiting for SSH connectivity...
# ✓ Waiting for cloud-init completion...
# ✓ Environment provisioned successfully
#
# Provisioning Details:
#  1. Environment name: my-environment
#  2. Instance name: torrust-tracker-vm-my-environment
#  3. Instance IP: 10.140.190.42
#  ...
```

### Basic provisioning (JSON Output)

```bash
# Provision the environment with JSON output for automation
torrust-tracker-deployer provision my-environment --output-format json

# Output (structured JSON):
# {
#   "environment_name": "my-environment",
#   "instance_name": "torrust-tracker-vm-my-environment",
#   "instance_ip": "10.140.190.42",
#   "ssh_private_key_path": "/home/user/.ssh/id_rsa",
#   "ssh_public_key_path": "/home/user/.ssh/id_rsa.pub",
#   "ssh_username": "torrust",
#   "ssh_port": 22,
#   "provider": "lxd",
#   "domains": [],
#   "provisioned_at": "2026-02-16T13:38:02.446056727Z"
# }
```

### Provision and extract IP for automation

```bash
# Provision and immediately extract IP address
IP=$(torrust-tracker-deployer provision my-environment \
  --output-format json \
  --log-output file-only | jq -r '.instance_ip')

echo "Provisioned VM at: $IP"

# Use IP in subsequent automation
ansible-playbook -i "$IP," deploy.yml
```

### Provision multiple environments

```bash
# Development (local)
torrust-tracker-deployer provision dev-local

# Staging (cloud)
torrust-tracker-deployer provision staging --output-format json

# Production (Hetzner with JSON for automation)
torrust-tracker-deployer provision production -o json
```

## Output

The provision command creates provider-specific resources:

### LXD Provider

- **VM instance** - LXD virtual machine (`torrust-tracker-vm-<env-name>`)
- **LXD profile** - Custom profile with cloud-init configuration
- **Network configuration** - Bridged network with IP assignment
- **OpenTofu state** - Infrastructure state in `build/<env>/tofu/lxd/`

### Hetzner Provider

- **Cloud server** - Hetzner Cloud server instance
- **Firewall** - Hetzner firewall with SSH access
- **SSH key** - Uploaded SSH public key
- **OpenTofu state** - Infrastructure state in `build/<env>/tofu/hetzner/`

### Common Outputs (All Providers)

- **Ansible inventory** - Generated inventory in `build/<env>/ansible/`
- **Environment state update** - State file updated to "Provisioned"

## Next Steps

After provisioning:

```bash
# 1. Configure the infrastructure (install Docker, Docker Compose)
torrust-tracker-deployer configure my-environment

# 2. Verify infrastructure readiness
torrust-tracker-deployer test my-environment
```

## Troubleshooting

### Environment not found

**Problem**: Cannot find environment with the specified name

**Solution**: Verify the environment was created

```bash
# Check environment data directory exists
ls -la data/my-environment/

# If not, create the environment first
torrust-tracker-deployer create environment -f config.json
```

### LXD not initialized (LXD provider only)

**Problem**: LXD is not properly initialized

**Solution**: Initialize LXD

```bash
# Initialize LXD with default settings
sudo lxd init --auto

# Add your user to lxd group
sudo usermod -a -G lxd $USER
newgrp lxd
```

### OpenTofu not found

**Problem**: OpenTofu CLI is not installed or not in PATH

**Solution**: Install OpenTofu

```bash
# Install OpenTofu
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sudo bash

# Verify installation
tofu version
```

### SSH connection timeout

**Problem**: Cannot establish SSH connection to provisioned VM

**Solution**: Check network connectivity and cloud-init status

```bash
# Get VM IP address
lxc list

# Try to connect manually
ssh -i <path-to-private-key> torrust@<vm-ip>

# Check cloud-init status
lxc exec <instance-name> -- cloud-init status
```

### Port already in use

**Problem**: LXD profile or instance name already exists

**Solution**: Clean up existing resources

```bash
# List existing instances
lxc list

# Delete old instance if needed
lxc delete <instance-name> --force

# List profiles
lxc profile list

# Delete old profile if needed
lxc profile delete <profile-name>
```

## Common Use Cases

### Quick local development

```bash
# Create, provision, and configure in sequence
torrust-tracker-deployer create environment -f dev.json
torrust-tracker-deployer provision dev-local
torrust-tracker-deployer configure dev-local
```

### CI/CD pipeline

```bash
#!/bin/bash
set -e

ENV_NAME="ci-${CI_JOB_ID}"

# Create environment
torrust-tracker-deployer create environment -f ci-config.json

# Provision infrastructure
torrust-tracker-deployer provision ${ENV_NAME}

# Run tests...
# Cleanup is handled by destroy command
```

### Reprovisioning

If you need to reprovision (destroy and create again):

```bash
# Destroy existing environment
torrust-tracker-deployer destroy my-environment

# Create fresh environment
torrust-tracker-deployer create environment -f config.json

# Provision again
torrust-tracker-deployer provision my-environment
```

## Technical Details

### Generated Resources

**LXD Resources**:

- Instance: `torrust-tracker-vm-<environment-name>`
- Profile: `torrust-profile-<environment-name>`
- Network: Bridged network with DHCP

**File Artifacts**:

- OpenTofu files: `build/<env>/tofu/lxd/`
- Ansible inventory: `build/<env>/ansible/inventory.yml`
- Instance info: Stored in environment state

### Cloud-Init Configuration

The provisioned VM includes cloud-init configuration for:

- User account creation (SSH username from config)
- SSH key deployment (public key from config)
- Network configuration
- Initial system setup

## See Also

- [create]create.md - Create environment (prerequisite)
- [configure]configure.md - Configure infrastructure (next step)
- [test]test.md - Verify infrastructure
- [destroy]destroy.md - Clean up infrastructure