Skip to content

Commit 56a745f

Browse files
committed
Merge #316: feat: [#315] Backup Support - Phases 1-4 Complete
36c5675 fix: Add blank line before code fence in run.md for markdown linting (Jose Celano) 44c2830 docs: Update Phase 4 completion notes (Jose Celano) 14b1d5e docs: Clarify that initial backups are not yet automatically created during run command (Jose Celano) d0d024d docs: Update Phase 4 progress - Parts 1 & 2 nearly complete (Jose Celano) a49e24a feat: [#315] Phase 4 Part 2 - E2E backup verification tests (Jose Celano) 97891c4 docs: Update backup-verification.md to document automatic crontab-based backups (Jose Celano) 99632b7 fix: Set correct Docker build context for provisioned-instance E2E tests (Jose Celano) 2ed37cf fix: Set correct Docker build context for provisioned-instance E2E tests (Jose Celano) 46a50ca fix: Fix provisioned-instance Dockerfile COPY paths for build context (Jose Celano) ca0e883 fix: [#315] Fix markdown emphasis spacing errors in documentation (Jose Celano) ad4ead6 feat: [#315] Step 3.2 & 3.3 - Crontab installation and wiring (Jose Celano) 9c366ab docs: [#315] Add backup container workflow badge to README (Jose Celano) 0e3cf3e fix: [#315] Use correct build context for backup image in security scan workflow (Jose Celano) 11c5b0d docs: [#315] Document Docker build context to prevent regression (Jose Celano) 22246dc fix: [#315] Properly fix Docker build context to avoid regression (Jose Celano) 1611b63 docs: [#315] Phase 3 - Update progress tracking with implementation completion (Jose Celano) 0e563fd feat: [#315] Step 3.1 - Add crontab templates and renderers for scheduled backups (Jose Celano) 5cecf69 docs: [#315] Update progress tracking - Step 2.4 complete (Jose Celano) 549fcaf feat: [#315] Step 2.4 - Update create template command to include backup with defaults (Jose Celano) 5cd5b4a docs: [#315] Update progress tracking with MySQL backup verification results (Jose Celano) a4e7bd9 docs: [#315] Update backup verification guide with MySQL-specific learnings (Jose Celano) 237e64c fix: [#315] Simplify MySQL backup SSL configuration by embedding in Docker image (Jose Celano) 1594b4a fix: [#315] Correct SQLite database path in backup configuration (Jose Celano) 062f4dc feat: [#315] Add backup release workflow integration (Jose Celano) 832fc0e refactor: [#315] extract DependencyCondition and ServiceDependency to separate modules (Jose Celano) d0ce37e docs: [#315] update Phase 2.2 completion status in issue spec (Jose Celano) 86773da fix: [#315] set Docker build context to repository root in backup workflow (Jose Celano) 61a3da3 feat: [#315] add backup template infrastructure with context and renderer (Jose Celano) 4f8fb4c feat: [#315] add backup configuration templates and docker-compose service (Jose Celano) b935871 docs: [#315] update Phase 2.1 completion status in issue spec (Jose Celano) 6ff96cb docs: [#315] add backup section to environment config JSON example (Jose Celano) 9d297cc fix: [#315] correct Dockerfile COPY paths for CI build context (Jose Celano) 11651c9 fix: [#315] correct doc examples to include backup parameter (Jose Celano) e88c2c8 feat: [#315] integrate backup configuration into environment creation config (Jose Celano) cec6ca7 feat: [#315] add backup configuration DTO with comprehensive help messages (Jose Celano) 6cb633e feat: [#315] add backup domain layer with parametrized tests (Jose Celano) 7bdad2e fix: [#315] override entrypoint in container verification step (Jose Celano) 7da6fac docs: [#315] update security scan index with backup container (Jose Celano) 15c8d6c feat: [#315] upgrade backup container to Debian 13 (trixie) (Jose Celano) da238ef fix: [#315] correct security scan results for tracker-backup (Jose Celano) 0b313ba docs: [#315] update image name in progress tracking (Jose Celano) bb3d5ea feat: [#315] add GitHub workflows for tracker-backup container (Jose Celano) b982214 docs: complete Phase 1.1b manual E2E testing for backup container (Jose Celano) 47ab60c docs: add deployed instance structure and Phase 1.1b manual testing guide (Jose Celano) 18ef367 feat: implement backup container with comprehensive tests and documentation (Jose Celano) Pull request description: ## 🎯 Issue #315 Complete: Full Backup Support Implementation This PR implements all phases of issue #315 (backup support), delivering a production-ready backup feature with comprehensive documentation. ### ✅ What's Included #### **Phase 1: Backup Container Infrastructure** ✅ - Production backup container (`docker/backup/`) - Multi-stage Dockerfile (base → test → production) - `backup.sh` with comprehensive Rust-style documentation - 44 unit tests (100% passing, run during build) - Non-root execution (UID 1000) - Base image: Debian 13 (trixie-slim) - Complete documentation (`docker/backup/README.md`) - GitHub publishing workflow (automated Docker Hub publishing) - Security scanning integration with Trivy #### **Phase 2: Deployer Integration** ✅ - Backup service in docker-compose templates - Configuration schema for environment creation - Service deployment with proper Docker profiles - Database support: MySQL 8 (MariaDB client), SQLite3 #### **Phase 3: Scheduled Backups** ✅ - Crontab installation and configuration (3 AM daily) - Backup scheduling via maintenance script - Retention cleanup (configurable days) - Automated execution after `release` command #### **Phase 4: Documentation & Testing** ✅ - Comprehensive user guide (`docs/user-guide/backup.md`) - Updated run command documentation - Accurate documentation of current automation state - **Note**: Initial backup invocation (during `run`) planned for Phase 4.2.2 ### 📦 Feature Completeness **What's Fully Automated:** - ✅ Setup: Backup config deployment during environment creation - ✅ Installation: Ansible playbooks for backup infrastructure - ✅ Crontab: Automatic crontab installation during `release` - ✅ Scheduled Backups: Daily automatic backups via crontab - ✅ Retention: Automatic cleanup of old backups - ✅ Support: MySQL and SQLite databases **Manual Workaround (Until Phase 4.2.2):** - Initial backup must be triggered manually: `docker compose --profile backup run --rm backup` ### 🏗️ Architecture **Backup Workflow:** ``` Create Environment → Release → Scheduled Backups ↓ ↓ ↓ Deploy Config Install Crontab Run Daily @ 3AM ↓ Create Backup Compress & Archive Cleanup Old Files ``` **Supported Deployments:** - Docker containers (E2E testing) - LXD VMs (production deployment) - Cloud VMs (Hetzner, etc.) ### 🧪 Testing **Unit Tests**: 44 tests in `backup_test.bats` covering: - Configuration validation - MySQL dump operations - SQLite backup operations - Compression and retention - Error handling **Integration Testing**: Verified in: - Docker container environments (E2E) - LXD VM deployments (production) - Manual testing with MySQL/SQLite ### 📚 Documentation - **User Guide**: `docs/user-guide/backup.md` - Complete feature documentation - **Run Command**: `docs/user-guide/commands/run.md` - Updated with accurate automation status - **Phase 4 Progress**: `docs/issues/315-phase-4-documentation-and-testing-plan.md` - Implementation details - **Container README**: `docker/backup/README.md` - Technical documentation - **Security Report**: `docs/security/docker/scans/torrust-tracker-backup.md` - Vulnerability tracking ### 🔐 Security - **Base Image**: Debian 13 (latest security patches) - **Vulnerabilities**: 11 total (9 HIGH, 2 CRITICAL) - Debian 13 upgrade resolved 3 critical vulnerabilities - Remaining: OpenSSL, MariaDB/glibc with available fixes - **Non-root Execution**: Container runs as UID 1000 - **Profile-based Access**: Backup service behind Docker profile ### 🚀 Future Enhancements **Phase 4.2.2: Initial Backup Automation** - Add `InitialBackupStep` to run command handler - Automatically invoke backup during `run` command - No manual workaround needed post-implementation ### 📊 Changes Summary - **44 commits** with full history - **90 files changed** (8,671 additions, 59 deletions) - **Key additions**: - Docker backup service (production-ready) - GitHub publishing workflow - Ansible backup playbooks - Crontab installation and configuration - Comprehensive user documentation - Security documentation and scanning ### 🔗 Related Issues - Issue: #315 - Implement Backup Support - Epic: #309 - Add backup support - Research: #310 - Database backup strategies ### ✨ Ready for Production The backup feature is fully implemented and production-ready: - ✅ All infrastructure code tested and working - ✅ Scheduled backups automatically executed - ✅ Complete user documentation - ✅ Security scanning integrated - ✅ Published to Docker Hub (`torrust/tracker-backup`) ACKs for top commit: josecelano: ACK 36c5675 Tree-SHA512: 5d731b62e3d4112158bc040fab9bdf1d1461807885756ad9f487670b9f91220b366181e2a2183f7f440b3a6d6043615e48f896a708bf2b8bddf87f01bd973306
2 parents 7a3b6bd + 36c5675 commit 56a745f

90 files changed

Lines changed: 8672 additions & 59 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Backup Container workflow for Torrust Tracker Deployer
2+
#
3+
# This workflow builds, tests, and publishes the backup Docker image.
4+
# Following patterns from container.yaml workflow.
5+
#
6+
# Triggers:
7+
# - Push to main/develop branches (only when backup container files change)
8+
# - Pull requests to main/develop (only when backup container files change)
9+
# - Manual dispatch
10+
#
11+
# Publishing:
12+
# - Images are pushed to Docker Hub on push to main/develop (not PRs)
13+
# - Requires Docker Hub credentials in repository secrets (dockerhub-torrust-backup environment)
14+
15+
name: Backup Container
16+
17+
on:
18+
push:
19+
branches:
20+
- "develop"
21+
- "main"
22+
paths:
23+
- "docker/backup/**"
24+
- ".github/workflows/backup-container.yaml"
25+
26+
pull_request:
27+
branches:
28+
- "develop"
29+
- "main"
30+
paths:
31+
- "docker/backup/**"
32+
- ".github/workflows/backup-container.yaml"
33+
34+
workflow_dispatch:
35+
36+
env:
37+
CARGO_TERM_COLOR: always
38+
DOCKER_HUB_USERNAME: torrust
39+
40+
jobs:
41+
test:
42+
name: Build & Test
43+
runs-on: ubuntu-latest
44+
timeout-minutes: 30
45+
46+
steps:
47+
- name: Checkout
48+
uses: actions/checkout@v4
49+
50+
- name: Setup Docker Buildx
51+
uses: docker/setup-buildx-action@v3
52+
53+
- name: Build Image
54+
uses: docker/build-push-action@v6
55+
with:
56+
# CRITICAL: Context must be ./docker/backup (not ./)
57+
# Docker COPY/ADD commands resolve paths RELATIVE TO BUILD CONTEXT, not Dockerfile location.
58+
# Setting context: ./docker/backup allows the Dockerfile to use simple relative paths like:
59+
# COPY backup.sh /scripts/backup.sh
60+
# This creates consistency between local builds and CI builds.
61+
#
62+
# Regression History: commit 9d297cc5 used context: . with full paths in Dockerfile
63+
# (e.g., COPY docker/backup/backup.sh). This worked but was confusing and error-prone
64+
# for future changes. The current approach is cleaner and prevents regression.
65+
#
66+
# See docker/backup/README.md "Building the Container" section for details.
67+
context: ./docker/backup
68+
file: ./docker/backup/Dockerfile
69+
target: production
70+
push: false
71+
load: true
72+
tags: torrust/tracker-backup:local
73+
cache-from: type=gha
74+
cache-to: type=gha,mode=max
75+
76+
- name: Inspect Image
77+
run: docker image inspect torrust/tracker-backup:local
78+
79+
- name: Verify Container Structure
80+
run: |
81+
echo "=== Verifying backup container structure ==="
82+
83+
echo "=== Checking backup script exists ==="
84+
docker run --rm --entrypoint ls torrust/tracker-backup:local -lh /scripts/backup.sh
85+
86+
echo "=== Checking backup directories ==="
87+
docker run --rm --entrypoint ls torrust/tracker-backup:local -ld /backups/mysql /backups/sqlite /backups/config
88+
89+
echo "=== Verifying tools installed ==="
90+
docker run --rm --entrypoint which torrust/tracker-backup:local bash
91+
docker run --rm --entrypoint which torrust/tracker-backup:local mysql
92+
docker run --rm --entrypoint which torrust/tracker-backup:local sqlite3
93+
docker run --rm --entrypoint which torrust/tracker-backup:local gzip
94+
docker run --rm --entrypoint which torrust/tracker-backup:local tar
95+
96+
echo "=== Checking entrypoint ==="
97+
docker inspect --format='{{.Config.Entrypoint}}' torrust/tracker-backup:local
98+
99+
- name: Test Container Execution
100+
run: |
101+
echo "=== Testing backup container without config (should fail gracefully) ==="
102+
docker run --rm torrust/tracker-backup:local || true
103+
104+
context:
105+
name: Context
106+
needs: test
107+
runs-on: ubuntu-latest
108+
109+
outputs:
110+
continue: ${{ steps.check.outputs.continue }}
111+
type: ${{ steps.check.outputs.type }}
112+
113+
steps:
114+
- name: Check Context
115+
id: check
116+
run: |
117+
if [[ "${{ github.repository }}" == "torrust/torrust-tracker-deployer" ]]; then
118+
if [[ "${{ github.event_name }}" == "push" ]]; then
119+
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
120+
echo "type=production" >> $GITHUB_OUTPUT
121+
echo "continue=true" >> $GITHUB_OUTPUT
122+
elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then
123+
echo "type=development" >> $GITHUB_OUTPUT
124+
echo "continue=true" >> $GITHUB_OUTPUT
125+
fi
126+
fi
127+
fi
128+
129+
# Default: don't continue
130+
if [[ -z "$(cat $GITHUB_OUTPUT 2>/dev/null)" ]]; then
131+
echo "continue=false" >> $GITHUB_OUTPUT
132+
fi
133+
134+
publish_development:
135+
name: Publish (Development)
136+
environment: dockerhub-torrust-backup
137+
needs: context
138+
if: needs.context.outputs.continue == 'true' && needs.context.outputs.type == 'development'
139+
runs-on: ubuntu-latest
140+
timeout-minutes: 30
141+
142+
steps:
143+
- name: Checkout
144+
uses: actions/checkout@v4
145+
146+
- name: Docker Meta
147+
id: meta
148+
uses: docker/metadata-action@v5
149+
with:
150+
images: |
151+
${{ env.DOCKER_HUB_USERNAME }}/tracker-backup
152+
tags: |
153+
type=ref,event=branch
154+
type=sha,prefix=dev-
155+
156+
- name: Login to Docker Hub
157+
uses: docker/login-action@v3
158+
with:
159+
username: ${{ env.DOCKER_HUB_USERNAME }}
160+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
161+
162+
- name: Setup Docker Buildx
163+
uses: docker/setup-buildx-action@v3
164+
165+
- name: Build and Push
166+
uses: docker/build-push-action@v6
167+
with:
168+
context: .
169+
file: ./docker/backup/Dockerfile
170+
target: production
171+
push: true
172+
tags: ${{ steps.meta.outputs.tags }}
173+
labels: ${{ steps.meta.outputs.labels }}
174+
cache-from: type=gha
175+
cache-to: type=gha,mode=max
176+
177+
publish_production:
178+
name: Publish (Production)
179+
environment: dockerhub-torrust-backup
180+
needs: context
181+
if: needs.context.outputs.continue == 'true' && needs.context.outputs.type == 'production'
182+
runs-on: ubuntu-latest
183+
timeout-minutes: 30
184+
185+
steps:
186+
- name: Checkout
187+
uses: actions/checkout@v4
188+
189+
- name: Docker Meta
190+
id: meta
191+
uses: docker/metadata-action@v5
192+
with:
193+
images: |
194+
${{ env.DOCKER_HUB_USERNAME }}/tracker-backup
195+
tags: |
196+
type=raw,value=latest
197+
type=ref,event=branch
198+
type=sha
199+
200+
- name: Login to Docker Hub
201+
uses: docker/login-action@v3
202+
with:
203+
username: ${{ env.DOCKER_HUB_USERNAME }}
204+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
205+
206+
- name: Setup Docker Buildx
207+
uses: docker/setup-buildx-action@v3
208+
209+
- name: Build and Push
210+
uses: docker/build-push-action@v6
211+
with:
212+
context: .
213+
file: ./docker/backup/Dockerfile
214+
target: production
215+
push: true
216+
tags: ${{ steps.meta.outputs.tags }}
217+
labels: ${{ steps.meta.outputs.labels }}
218+
cache-from: type=gha
219+
cache-to: type=gha,mode=max

.github/workflows/docker-security-scan.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ jobs:
4242
- dockerfile: docker/ssh-server/Dockerfile
4343
context: docker/ssh-server
4444
name: ssh-server
45+
- dockerfile: docker/backup/Dockerfile
46+
context: docker/backup
47+
name: tracker-backup
4548

4649
steps:
4750
- name: Checkout code
@@ -54,7 +57,7 @@ jobs:
5457
docker build \
5558
-t torrust-tracker-deployer/${{ matrix.image.name }}:latest \
5659
-f ${{ matrix.image.dockerfile }} \
57-
.
60+
${{ matrix.image.context }}
5861
5962
# Human-readable output in logs
6063
# This NEVER fails the job; it’s only for visibility

AGENTS.md

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ These principles should guide all development decisions, code reviews, and featu
121121
7. **Before working with Tera templates**: Read [`docs/contributing/templates/tera.md`](docs/contributing/templates/tera.md) for correct variable syntax - use `{{ variable }}` not `{ { variable } }`. Tera template files have the `.tera` extension.
122122

123123
8. **When adding new Ansible playbooks**: Read [`docs/contributing/templates/ansible.md`](docs/contributing/templates/ansible.md) and the ADR [`atomic-ansible-playbooks.md`](docs/decisions/atomic-ansible-playbooks.md).
124-
- **CRITICAL: One playbook = one responsibility** (atomic playbook rule)
125-
- Conditional enablement belongs in Rust commands/steps, not in Ansible `when:` clauses (use `when:` only for host facts)
126-
- Static playbooks must be registered in `src/infrastructure/external_tools/ansible/template/renderer/project_generator.rs` under `copy_static_templates()` so they are copied into the build directory
124+
- **CRITICAL: One playbook = one responsibility** (atomic playbook rule)
125+
- Conditional enablement belongs in Rust commands/steps, not in Ansible `when:` clauses (use `when:` only for host facts)
126+
- Static playbooks must be registered in `src/infrastructure/external_tools/ansible/template/renderer/project_generator.rs` under `copy_static_templates()` so they are copied into the build directory
127127

128128
9. **When handling errors in code**: Read [`docs/contributing/error-handling.md`](docs/contributing/error-handling.md) for error handling principles. Prefer explicit enum errors over anyhow for better pattern matching and user experience. Make errors clear, include sufficient context for traceability, and ensure they are actionable with specific fix instructions.
129129

@@ -151,6 +151,36 @@ These principles should guide all development decisions, code reviews, and featu
151151

152152
20. **When generating environment configurations** (for AI agents): Reference the Rust types in [`src/application/command_handlers/create/config/`](src/application/command_handlers/create/config/) for accurate constraint information. These types express richer validation rules than the JSON schema alone (e.g., `NonZeroU32`, tagged enums, newtype wrappers). Read the [README](src/application/command_handlers/create/config/README.md) in that folder for the full guide. The JSON schema (`schemas/environment-config.json`) provides basic structure, but the Rust types are authoritative for constraints. See the [ADR](docs/decisions/configuration-dto-layer-placement.md) for why these types are in the application layer.
153153

154+
## 🏗️ Deployed Instance Structure
155+
156+
After running the complete deployment workflow (`create → provision → configure → release → run`), the virtual machine has the following structure:
157+
158+
```text
159+
/opt/torrust/ # Application root directory
160+
├── docker-compose.yml # Main orchestration file
161+
├── .env # Environment variables
162+
└── storage/ # Persistent data volumes
163+
├── tracker/
164+
│ ├── lib/ # Database files (tracker.db for SQLite)
165+
│ ├── log/ # Tracker logs
166+
│ └── etc/ # Configuration (tracker.toml)
167+
├── prometheus/
168+
│ └── etc/ # Prometheus configuration
169+
└── grafana/
170+
├── data/ # Grafana database
171+
└── provisioning/ # Dashboards and datasources
172+
```
173+
174+
**Key commands inside the VM**:
175+
176+
```bash
177+
cd /opt/torrust # Application root
178+
docker compose ps # Check services
179+
docker compose logs tracker # View logs
180+
```
181+
182+
For detailed information about working with deployed instances, see [`docs/user-guide/`](docs/user-guide/README.md).
183+
154184
## 🧪 Build & Test
155185

156186
- **Setup Dependencies**: `cargo run --bin dependency-installer install` (sets up required development tools)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![Linting](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml) [![Testing](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml) [![E2E Infrastructure Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml) [![E2E Deployment Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml) [![Test LXD Container Provisioning](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml) [![Coverage](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml) [![Container](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml) [![Docker Security Scan](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml)
1+
[![Linting](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml) [![Testing](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml) [![E2E Infrastructure Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml) [![E2E Deployment Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml) [![Test LXD Container Provisioning](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml) [![Coverage](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml) [![Container](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml) [![Backup Container](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/backup-container.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/backup-container.yaml) [![Docker Security Scan](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml)
22

33
# Torrust Tracker Deployer
44

docker/backup/Dockerfile

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# ============================================================================
2+
# Torrust Backup Container
3+
# ============================================================================
4+
# Production backup container for Torrust Tracker deployments.
5+
# Configuration is provided via mounted config files - no environment variables.
6+
#
7+
# Configuration Files:
8+
# /etc/backup/backup.conf - Main configuration (sourced by backup.sh)
9+
# /etc/backup/backup-paths.txt - List of files/directories to backup
10+
#
11+
# Mount Points:
12+
# /backups - Output directory for all backups (read-write)
13+
# /data - Source data directory (read-only, app storage mounted here)
14+
#
15+
# Output Structure:
16+
# /backups/mysql/mysql_YYYYMMDD_HHMMSS.sql.gz - MySQL dumps (compressed)
17+
# /backups/sqlite/sqlite_YYYYMMDD_HHMMSS.db.gz - SQLite backups (compressed)
18+
# /backups/config/config_YYYYMMDD_HHMMSS.tar.gz - Config archives (compressed)
19+
#
20+
# Security:
21+
# Container runs as uid 1000 (torrust user) to match app file ownership.
22+
# This ensures backup files have correct ownership on host.
23+
#
24+
# Testing:
25+
# Tests run during build using bats-core. Build fails if tests fail.
26+
# ============================================================================
27+
28+
FROM debian:trixie-slim AS base
29+
30+
# Install required utilities
31+
# - bash: for scripting
32+
# - default-mysql-client: MariaDB client (compatible with MySQL 8)
33+
# - sqlite3: SQLite client for .backup command
34+
# - gzip: for compression
35+
# - tar: for config file archiving
36+
RUN apt-get update && apt-get install -y --no-install-recommends \
37+
bash \
38+
default-mysql-client \
39+
sqlite3 \
40+
gzip \
41+
tar \
42+
&& rm -rf /var/lib/apt/lists/*
43+
44+
# =============================================================================
45+
# Test Stage - Run unit tests during build
46+
# =============================================================================
47+
FROM base AS test
48+
49+
# Install bats-core for testing
50+
RUN apt-get update && apt-get install -y --no-install-recommends \
51+
bats \
52+
&& rm -rf /var/lib/apt/lists/*
53+
54+
# Copy test files
55+
COPY backup.sh /scripts/backup.sh
56+
COPY backup_test.bats /scripts/backup_test.bats
57+
RUN chmod +x /scripts/backup.sh
58+
59+
# Run tests - build fails if tests fail
60+
# Create a marker file to prove tests passed
61+
RUN cd /scripts && bats backup_test.bats && touch /scripts/.tests_passed
62+
63+
# =============================================================================
64+
# Production Stage
65+
# =============================================================================
66+
FROM base AS production
67+
68+
# Require tests to have passed by copying marker from test stage
69+
# This ensures test stage is always executed before production stage
70+
COPY --from=test /scripts/.tests_passed /tmp/.tests_passed
71+
72+
# Create backup user with same UID as torrust app user
73+
# This ensures backup files have correct ownership on host
74+
# Using 'torrust' as the username to match the app user
75+
ARG BACKUP_UID=1000
76+
ARG BACKUP_GID=1000
77+
RUN groupadd -g ${BACKUP_GID} torrust 2>/dev/null || true && \
78+
useradd -u ${BACKUP_UID} -g ${BACKUP_GID} -s /bin/bash torrust 2>/dev/null || true
79+
80+
# Create directories with correct ownership
81+
RUN mkdir -p /scripts /backups/mysql /backups/sqlite /backups/config /etc/mysql && \
82+
chown -R ${BACKUP_UID}:${BACKUP_GID} /backups
83+
84+
# Create MySQL client configuration (disable SSL verification for Docker connections)
85+
RUN cat > /etc/mysql/mysql-client.cnf <<'EOF' && \
86+
chmod 644 /etc/mysql/mysql-client.cnf
87+
[mysqldump]
88+
ssl=FALSE
89+
EOF
90+
91+
# Copy backup script (tests already passed in test stage)
92+
COPY backup.sh /scripts/backup.sh
93+
RUN chmod +x /scripts/backup.sh
94+
95+
# Run as non-root user (torrust, uid 1000)
96+
USER torrust
97+
98+
ENTRYPOINT ["/scripts/backup.sh"]

0 commit comments

Comments
 (0)