Skip to content

Commit 3f6b4e4

Browse files
committed
Merge #276: feat: [#275] Change Grafana default port from 3100 to 3000
c038170 feat: [#275] change Grafana default port from 3100 to 3000 (Jose Celano) Pull request description: ## Summary Changes the Grafana default port from 3100 to 3000, aligning with the industry standard. ## Changes - **Template**: Updated `docker-compose.yml.tera` to expose Grafana on port 3000 - **Source code**: Updated `DEFAULT_GRAFANA_PORT` constant and all test assertions - **Documentation**: Updated all active docs referencing port 3100 - **ADR**: Added `grafana-default-port-3000.md` documenting the decision ## Files Modified ### Template - `templates/docker-compose/docker-compose.yml.tera` - Port mapping `3100:3000` → `3000:3000` ### Rust Source - `src/infrastructure/remote_actions/validators/grafana.rs` - Constant and docs - `src/application/command_handlers/show/info/grafana.rs` - URL generation and tests - `src/presentation/views/commands/show/environment_info/grafana.rs` - Test fixtures - `src/testing/e2e/tasks/run_run_validation.rs` - Error messages ### Documentation - 10 documentation files updated (user guides, security docs, ADRs, e2e testing) - New ADR created: `docs/decisions/grafana-default-port-3000.md` ## Rationale Port 3000 is the industry standard for Grafana. The original use of port 3100 was a workaround for macOS port conflicts, but: 1. Port 3000 never actually conflicted with macOS services 2. Users expect Grafana on port 3000 3. Most documentation and tutorials use port 3000 ## Testing - ✅ All 1848 unit tests pass - ✅ All 15 integration tests pass - ✅ Clippy lint check passes - ✅ Rustfmt check passes - ✅ Markdown lint passes Fixes #275 ACKs for top commit: josecelano: ACK c038170 Tree-SHA512: 556780a207bb71a0875d778b4d14e388c10fcb06f3c3513c1cafa310372c5679be2073c4842c059bc96f43c1775a9dd4839d53b9900cc145375cdbc50c195abb
2 parents 50e3ee9 + c038170 commit 3f6b4e4

16 files changed

Lines changed: 170 additions & 104 deletions

File tree

docs/analysis/security/docker-network-segmentation-analysis.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ graph LR
7676
subgraph VM["Docker Host VM"]
7777
subgraph PublicServices["Publicly Exposed Services"]
7878
Tracker[Tracker<br/>Ports: 6969/udp, 7070, 1212]
79-
Grafana[Grafana<br/>Port: 3100]
79+
Grafana[Grafana<br/>Port: 3000]
8080
end
8181
8282
subgraph InternalServices["Internal Services<br/>(No external exposure)"]
@@ -107,12 +107,10 @@ graph LR
107107
**IMPORTANT**: The Tracker service stores MySQL database credentials in multiple locations:
108108

109109
1. **Environment Variables**:
110-
111110
- `MYSQL_ROOT_PASSWORD` (injected from `.env` file)
112111
- Other database connection parameters
113112

114113
2. **Configuration Files**:
115-
116114
- `torrust-tracker.toml` or equivalent configuration may contain database credentials
117115
- Mounted at `./storage/tracker/etc:/etc/torrust/tracker:Z`
118116

@@ -265,24 +263,20 @@ ports:
265263
**Answer**: **YES - Network segmentation still provides significant value**:
266264

267265
1. **Isolates Grafana from Database**:
268-
269266
- Grafana has NO MySQL credentials
270267
- Compromising Grafana alone cannot access database
271268
- Attacker must compromise BOTH Grafana AND Tracker to reach MySQL
272269

273270
2. **Reduces Attack Surface**:
274-
275271
- Only 1 service (Tracker) can access MySQL, not 3 services
276272
- Fewer pathways to database = fewer opportunities for attackers
277273

278274
3. **Defense in Depth**:
279-
280275
- Network segmentation is ONE layer
281276
- Even if attacker has credentials, they need network access
282277
- Grafana compromise doesn't automatically give MySQL network access
283278

284279
4. **Limits Lateral Movement**:
285-
286280
- Compromised Grafana cannot pivot to MySQL
287281
- Compromised Prometheus cannot pivot to MySQL
288282
- Attack chain must go: Grafana → Tracker → MySQL (3 steps, not 2)
@@ -293,13 +287,11 @@ ports:
293287

294288
**Revised Risk Matrix WITH Network Segmentation**:
295289

296-
1. **Attacker compromises Grafana** (public service at port 3100)
297-
290+
1. **Attacker compromises Grafana** (public service at port 3000)
298291
- Exploits a Grafana vulnerability (CVE in grafana/grafana image)
299292
- Gains shell access inside Grafana container
300293

301294
2. **Lateral movement via shared network**
302-
303295
- From Grafana container: `telnet mysql 3306` - **succeeds** ❌
304296
- Attacker can now attempt MySQL authentication attacks
305297
- Attacker can perform network reconnaissance: `nmap -p- mysql`
@@ -372,7 +364,6 @@ Beyond network segmentation, implement these credential protection measures:
372364
```
373365

374366
5. **Monitor Credential Access**:
375-
376367
- Log all MySQL connection attempts
377368
- Alert on failed authentication
378369
- Audit container exec commands
@@ -514,13 +505,11 @@ services:
514505
### Testing Requirements
515506

516507
1. **Positive Tests** (these MUST work):
517-
518508
- Tracker can connect to MySQL
519509
- Prometheus can scrape metrics from Tracker
520510
- Grafana can query Prometheus
521511

522512
2. **Negative Tests** (these MUST fail):
523-
524513
- Grafana CANNOT reach MySQL: `docker exec grafana ping mysql` → fails
525514
- Grafana CANNOT reach Tracker: `docker exec grafana curl tracker:1212` → fails
526515
- Prometheus CANNOT reach MySQL: `docker exec prometheus ping mysql` → fails

docs/decisions/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This directory contains architectural decision records for the Torrust Tracker D
66

77
| Status | Date | Decision | Summary |
88
| ------------- | ---------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
9+
| ✅ Accepted | 2026-01-20 | [Grafana Default Port 3000](./grafana-default-port-3000.md) | Use Grafana's default port 3000 instead of 3100 for dedicated VM deployments |
910
| ✅ Accepted | 2026-01-20 | [Caddy for TLS Termination](./caddy-for-tls-termination.md) | Use Caddy v2.10 as TLS proxy for automatic HTTPS with WebSocket support |
1011
| ✅ Accepted | 2026-01-20 | [Per-Service TLS Configuration](./per-service-tls-configuration.md) | Use domain + use_tls_proxy fields instead of nested tls section for explicit TLS opt-in |
1112
| ✅ Accepted | 2026-01-20 | [Uniform HTTP Tracker TLS Requirement](./uniform-http-tracker-tls-requirement.md) | All HTTP trackers must use same TLS setting due to tracker's global on_reverse_proxy |

docs/decisions/docker-ufw-firewall-security-strategy.md

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ tracker:
8787

8888
grafana:
8989
ports:
90-
- "3100:3000" # Grafana UI - public
90+
- "3000:3000" # Grafana UI - public
9191

9292
# Host-accessible services - bind to localhost only
9393
prometheus:
@@ -407,19 +407,16 @@ Docker's built-in security features we rely on:
407407
**Test Strategy**:
408408

409409
1. **External Port Scanning**:
410-
411410
- Use `nmap` or `netstat` from external host to scan VM's public IP
412411
- Verify ONLY expected ports are open (SSH, tracker UDP/HTTP, Grafana)
413412
- Verify internal service ports (Prometheus 9090, MySQL 3306) are NOT accessible
414413

415414
2. **Docker Network Inspection**:
416-
417415
- Query `docker port <container>` to list published ports
418416
- Parse `docker inspect` output to verify port bindings match specification
419417
- Fail if any internal service has unexpected `HostPort` mapping
420418

421419
3. **Service Accessibility Tests**:
422-
423420
- **From external host**: Verify public services respond (tracker, Grafana)
424421
- **From external host**: Verify internal services timeout/refuse (Prometheus, MySQL)
425422
- **From Docker host**: Verify localhost bindings work (`curl 127.0.0.1:9090` succeeds)
@@ -447,7 +444,7 @@ fn it_should_block_external_access_to_prometheus_when_deployed() {
447444
fn it_should_allow_external_access_to_grafana_when_deployed() {
448445
// 1. Deploy environment with Grafana
449446
// 2. Get VM public IP and configured Grafana port
450-
// 3. Connect to http://<vm-ip>:3100
447+
// 3. Connect to http://<vm-ip>:3000
451448
// 4. Assert successful connection and Grafana login page
452449
}
453450

@@ -548,26 +545,22 @@ This section addresses critical security questions about the chosen approach.
548545
**✅ Meets Best Practices**:
549546

550547
1. **Defense in Depth**:
551-
552548
- UFW firewall (host level)
553549
- Docker port bindings (service level)
554550
- Docker network segmentation (lateral movement prevention)
555551
- Application authentication (Grafana, Tracker API)
556552

557553
2. **Principle of Least Privilege**:
558-
559554
- Services only have network access to what they need
560555
- Non-root container users
561556
- Minimal capabilities
562557

563558
3. **Security by Default**:
564-
565559
- Internal services not exposed (MySQL, Prometheus)
566560
- Public services explicitly configured
567561
- Localhost-only bindings for debugging services
568562

569563
4. **Auditability**:
570-
571564
- Explicit port bindings documented
572565
- Network topology documented in templates
573566
- Security decisions documented in ADR
@@ -607,7 +600,7 @@ This section addresses critical security questions about the chosen approach.
607600
fn it_should_detect_unexpected_port_exposure() {
608601
// Run nmap scan from external host
609602
// Parse open ports
610-
// Assert only expected ports open (22, 6969, 7070, 1212, 3100)
603+
// Assert only expected ports open (22, 6969, 7070, 1212, 3000)
611604
// Fail if MySQL (3306) or Prometheus (9090) detected
612605
}
613606
```
@@ -618,7 +611,7 @@ This section addresses critical security questions about the chosen approach.
618611
#!/bin/bash
619612
# scripts/check-port-exposure.sh
620613

621-
EXPECTED_PORTS="22,6969,7070,1212,3100"
614+
EXPECTED_PORTS="22,6969,7070,1212,3000"
622615
FORBIDDEN_PORTS="3306,9090"
623616

624617
# Check docker ps for published ports
@@ -651,7 +644,7 @@ This section addresses critical security questions about the chosen approach.
651644
# 6969 (UDP tracker)
652645
# 7070 (HTTP tracker)
653646
# 1212 (REST API)
654-
# 3100 (Grafana)
647+
# 3000 (Grafana)
655648

656649
# Unexpected = security issue
657650
```
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Decision: Use Grafana's Default Port 3000 Instead of 3100
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Date
8+
9+
2026-01-20
10+
11+
## Context
12+
13+
The Grafana service configuration was originally copied from the [Torrust Demo project](https://github.com/torrust/torrust-demo), which uses port 3100 on the host to avoid conflicts with other services commonly using port 3000 (like Node.js development servers that typically run on port 3000).
14+
15+
In the Torrust Demo environment, this port offset was necessary because:
16+
17+
- The demo runs multiple services that might conflict with development tools
18+
- Users often have Node.js applications running on port 3000
19+
- The demo is frequently run on developer machines with other services active
20+
21+
However, in the Torrust Tracker Deployer context:
22+
23+
- Deployments target dedicated VM instances, not developer machines
24+
- No other services in our stack use port 3000
25+
- Users expect Grafana to be on its default port
26+
- The port offset (3100 vs 3000) causes confusion when consulting Grafana documentation
27+
28+
## Decision
29+
30+
Change the Grafana host port from `3100` to `3000`, matching Grafana's internal default port.
31+
32+
**Before:**
33+
34+
```yaml
35+
grafana:
36+
ports:
37+
- "3100:3000" # Host:Container
38+
```
39+
40+
**After:**
41+
42+
```yaml
43+
grafana:
44+
ports:
45+
- "3000:3000" # Host:Container (using Grafana's default port)
46+
```
47+
48+
## Rationale
49+
50+
1. **Simplicity**: Using `3000:3000` is more intuitive than `3100:3000` - host and container ports match
51+
2. **No Conflict**: In our deployment context (dedicated VMs), there's no service using port 3000
52+
3. **Documentation Alignment**: Grafana's official documentation uses port 3000; matching this reduces confusion
53+
4. **User Expectations**: Users familiar with Grafana expect it on port 3000
54+
5. **Reduced Cognitive Load**: One less port mapping to remember
55+
56+
## Consequences
57+
58+
### Positive
59+
60+
- More intuitive port configuration (same port inside and outside container)
61+
- Better alignment with Grafana's official documentation
62+
- Simpler mental model for users
63+
- Consistent with how most Grafana installations are configured
64+
65+
### Negative
66+
67+
- **Breaking Change for Existing Deployments**: Users with existing deployments using port 3100 will need to update their bookmarks, scripts, and firewall rules
68+
- **Migration Required**: Existing environments need to be re-released to apply the new port configuration
69+
70+
### Migration Path
71+
72+
For existing deployments:
73+
74+
1. Run `release <environment>` to regenerate docker-compose files with new port
75+
2. Run `run <environment>` to restart services with new configuration
76+
3. Update any bookmarks, scripts, or monitoring that reference port 3100
77+
78+
## Alternatives Considered
79+
80+
### Keep Port 3100
81+
82+
**Pros**: No breaking change for existing users
83+
84+
**Cons**: Perpetuates unnecessary complexity inherited from a different project context
85+
86+
**Decision**: Rejected - the benefits of using the default port outweigh the one-time migration cost
87+
88+
### Make Port Configurable
89+
90+
**Pros**: Maximum flexibility
91+
92+
**Cons**: Adds configuration complexity for a setting rarely needed; violates "convention over configuration" principle
93+
94+
**Decision**: Rejected - not worth the added complexity; users with special needs can fork/modify
95+
96+
## Related
97+
98+
- [Issue #275](https://github.com/torrust/torrust-tracker-deployer/issues/275) - Implementation tracking
99+
- [ADR: Grafana Integration Pattern](grafana-integration-pattern.md) - Original Grafana integration decision
100+
- [Torrust Demo](https://github.com/torrust/torrust-demo) - Original source of the 3100 port choice

0 commit comments

Comments
 (0)