This commit is contained in:
2026-03-22 12:03:20 +01:00
commit ece5fd817a

244
README.md Normal file
View File

@@ -0,0 +1,244 @@
# Nextcloud Self-hosted VPS
Docker Compose setup for a self-hosted VPS running Nextcloud, Gitea, and monitoring — managed as a GitOps-style repo.
## Prerequisites
- A VPS with SSH access
- Domain `t-gstone.de` with DNS control
- Git installed locally
Check your VPS OS:
```bash
cat /etc/os-release
```
## DNS Setup
Create these A records pointing to your VPS IP:
| Record | Value |
|--------|-------|
| `nextcloud.t-gstone.de` | `<VPS_IP>` |
| `git.t-gstone.de` | `<VPS_IP>` |
## Quick Start
```bash
# 1. Clone this repo on the VPS
git clone <repo-url> && cd nextcloud-selfhosted
# 2. Create .env files from examples
cp .env.example .env
cp nextcloud/.env.example nextcloud/.env
cp gitea/.env.example gitea/.env
cp monitoring/.env.example monitoring/.env
# 3. Edit each .env file with real values
# - Generate strong passwords for Postgres and Nextcloud admin
# - Add Grafana Cloud credentials to monitoring/.env
# 4. Deploy
./deploy.sh
```
## Services
| Service | Subdomain | Stack |
|---------|-----------|-------|
| Nextcloud | `nextcloud.t-gstone.de` | Nextcloud + PostgreSQL 16 + Redis 7 |
| Gitea | `git.t-gstone.de` | Gitea (SQLite) |
| Caddy | — | Reverse proxy, auto HTTPS |
| Monitoring | — | Grafana Alloy -> Grafana Cloud |
## Data Layout
All persistent data lives under `/opt/docker-data/`:
```
/opt/docker-data/
├── caddy/data/ # TLS certificates
├── caddy/config/
├── nextcloud/html/ # Nextcloud application
├── nextcloud/data/ # User files
├── nextcloud/db/ # PostgreSQL data
├── gitea/data/ # Repositories + Gitea DB
└── gitea/config/ # Gitea configuration
```
## Managing Services
Each service has its own compose file and can be managed independently:
```bash
# Restart just Nextcloud
docker compose -f nextcloud/docker-compose.yml --env-file .env up -d
# View logs for Gitea
docker compose -f gitea/docker-compose.yml --env-file .env logs -f
# Stop everything
for svc in monitoring gitea nextcloud caddy; do
docker compose -f $svc/docker-compose.yml --env-file .env down
done
```
## Adding a New Service
1. Create a new directory: `mkdir myapp/`
2. Create `myapp/docker-compose.yml`:
- Join the `proxy` external network
- Bind mount data to `${DATA_ROOT}/myapp/`
- Add `myapp/.env.example` if the service needs secrets
3. Add a reverse proxy entry in `caddy/Caddyfile`:
```
myapp.t-gstone.de {
reverse_proxy myapp:8080
}
```
4. Reload Caddy: `docker exec caddy caddy reload --config /etc/caddy/Caddyfile`
5. Add a DNS A record for `myapp.t-gstone.de` -> VPS IP
6. Add data directory creation to `deploy.sh`
7. Add backup steps to `backup.sh` if the service has persistent data
## Backup & Restore
### Creating Backups
```bash
./backup.sh
```
This dumps the Nextcloud Postgres database, archives Nextcloud data/config and Gitea data, and stores them in `/opt/backups/` with date-stamped filenames. Backups older than 7 days are automatically removed.
Schedule daily backups:
```bash
crontab -e
# Add:
0 3 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1
```
### Restoring
```bash
./restore.sh 2026-03-22
```
This stops services, restores data from the specified date's backup files, restores the database, and restarts everything.
### Backup Strategy Options
The current setup stores backups locally on the same VPS. For production use, consider an off-site strategy:
| Option | Pros | Cons |
|--------|------|------|
| **Local only** (`/opt/backups/`) | Simplest, no extra cost | Lost if VPS dies |
| **rsync to second VPS or home server** | Simple, full control | Need a second machine |
| **S3-compatible object storage** (Backblaze B2, Hetzner Object Storage, Wasabi) | Cheap, durable, off-site | Monthly cost (~$0.005/GB) |
| **Restic or BorgBackup** to any remote target | Encrypted, deduplicated, incremental | More setup complexity |
Recommendation for a personal setup: **Backblaze B2 or Hetzner Object Storage with Restic**. Both offer free egress (B2) or low cost, and Restic handles encryption + deduplication automatically. A cron job running `restic backup` after `backup.sh` completes the pipeline.
## Monitoring
### Setup
1. Sign up at [grafana.com](https://grafana.com) (free tier)
2. Go to **My Account** -> **Grafana Cloud** -> your stack
3. Find your Loki and Prometheus endpoints + credentials
4. Fill in `monitoring/.env` with those values
5. Start the monitoring stack: `docker compose -f monitoring/docker-compose.yml --env-file .env up -d`
### Recommended Alerts
Set these up in Grafana Cloud UI (**Alerting** -> **Alert rules**):
| Alert | Condition | Severity |
|-------|-----------|----------|
| Disk usage high | `node_filesystem_avail_bytes` / `node_filesystem_size_bytes` < 0.2 | Critical |
| Container restarting | Container restart count > 3 in 10 min | Warning |
| High memory usage | `node_memory_MemAvailable_bytes` / `node_memory_MemTotal_bytes` < 0.1 | Warning |
| High CPU usage | `node_cpu_seconds_total` idle < 10% sustained 5 min | Warning |
| Nextcloud cron stale | No log line from `nextcloud-cron` in 15 min | Warning |
### Recommended Dashboards
Import these from [Grafana Dashboards](https://grafana.com/grafana/dashboards/):
- **Node Exporter Full** (ID: 1860) — CPU, memory, disk, network
- **Docker Container Monitoring** (ID: 893) — per-container resource usage
- **Loki Docker Logs** — search and filter container logs
## Security Hardening (Reference)
These steps are **not automated** — apply them manually based on your needs.
### Firewall (UFW)
```bash
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH (change if using non-default port)
ufw allow 80/tcp # HTTP (Caddy redirect)
ufw allow 443/tcp # HTTPS
ufw allow 443/udp # HTTPS (HTTP/3 / QUIC)
ufw allow 2222/tcp # Gitea SSH
ufw enable
```
### SSH Hardening
Edit `/etc/ssh/sshd_config`:
```
PasswordAuthentication no
PermitRootLogin prohibit-password
MaxAuthTries 3
```
Then: `systemctl restart sshd`
### fail2ban
```bash
apt install fail2ban
```
Create `/etc/fail2ban/jail.local`:
```ini
[sshd]
enabled = true
maxretry = 5
bantime = 3600
[nextcloud]
enabled = true
port = 80,443
filter = nextcloud
logpath = /opt/docker-data/nextcloud/data/nextcloud.log
maxretry = 5
bantime = 3600
```
Create `/etc/fail2ban/filter.d/nextcloud.conf`:
```ini
[Definition]
failregex = ^.*Login failed.*Remote IP.*<HOST>.*$
```
### Unattended Security Upgrades
```bash
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
```
### Docker Daemon
- Keep Docker updated: `apt update && apt upgrade docker-ce`
- Don't run containers as root unless necessary (Gitea uses rootless image)
- The Docker socket is mounted read-only into the Alloy container — be aware this still grants significant access