v_11
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
<div align="center">
|
||||
|
||||
# 📚 Documize Community — Docker Deployment
|
||||
|
||||
**Self-hosted knowledge management · v5.14.0 · MySQL 8 · Docker Compose**
|
||||
|
||||
[](https://github.com/documize/community)
|
||||
[](https://hub.docker.com/_/mysql)
|
||||
[](https://docs.docker.com/compose/)
|
||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
|
||||
A production-ready Docker Compose stack for [Documize Community](https://github.com/documize/community) — an open-source, self-hosted alternative to Confluence, built with Go + EmberJS.
|
||||
|
||||
[Quick Start](#-quick-start) · [Configuration](#-configuration) · [Architecture](#-architecture) · [Operations](#-day-to-day-operations) · [Troubleshooting](#-troubleshooting)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **Single binary deployment** — Documize ships as one statically-linked Go binary; no runtime dependencies beyond a database
|
||||
- **Zero reboot-loop risk** — Init-container pattern separates the one-time binary download from the always-running app container
|
||||
- **Full-text search** — MySQL configured with `ft-min-word-len=3` and `utf8mb4` collation as required by Documize
|
||||
- **Secret-free Compose file** — All credentials live in `.env`; `docker-compose.yml` contains no plaintext passwords
|
||||
- **Named volume persistence** — Both database data and the app binary survive container restarts and upgrades
|
||||
- **SMTP support** — Optional email notifications configured entirely via environment variables
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
| Requirement | Minimum version |
|
||||
|---|---|
|
||||
| Docker Engine | 24.0+ |
|
||||
| Docker Compose | v2.20+ (included with Docker Desktop) |
|
||||
| Available port | TCP `5001` (configurable) |
|
||||
| RAM | 512 MB free (1 GB recommended) |
|
||||
| Disk | 2 GB free |
|
||||
| Internet | Required on first start to download the Documize binary (~25 MB) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Clone or download this repository
|
||||
git clone https://github.com/your-org/documize-docker.git
|
||||
cd documize-docker
|
||||
|
||||
# 2. Create your environment file from the template
|
||||
cp .env.template .env
|
||||
|
||||
# 3. Generate and set your secrets
|
||||
echo "MYSQL_PASSWORD=$(openssl rand -base64 24)"
|
||||
echo "MYSQL_ROOT_PASSWORD=$(openssl rand -base64 24)"
|
||||
echo "DOCUMIZE_SALT=$(openssl rand -hex 32)"
|
||||
# Paste each value into .env
|
||||
|
||||
# 4. Protect the file and add to .gitignore
|
||||
chmod 600 .env
|
||||
echo ".env" >> .gitignore
|
||||
|
||||
# 5. Start the stack
|
||||
docker compose up -d
|
||||
|
||||
# 6. Watch the logs until Documize is ready (~30–60 s)
|
||||
docker compose logs -f
|
||||
|
||||
# 7. Open the setup wizard
|
||||
open http://localhost:5001
|
||||
```
|
||||
|
||||
> **Activation key** — The Community edition requires a free activation key. Register your email at [documize.com/community/get-started](https://www.documize.com/community/get-started) to receive one instantly.
|
||||
|
||||
---
|
||||
|
||||
## 🏗 Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Docker Host │
|
||||
│ │
|
||||
│ ┌──────────────┐ exits 0 ┌─────────────────────────┐ │
|
||||
│ │ documize-init│ ──────────► │ app_bin volume │ │
|
||||
│ │ (alpine:3.19)│ downloads │ /app/bin/documize │ │
|
||||
│ │ restart: no │ binary once └──────────┬──────────────┘ │
|
||||
│ └──────────────┘ │ mounts │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ documize-app (alpine:3.19) │ │
|
||||
│ │ restart: unless-stopped │ │
|
||||
│ │ exec /app/bin/documize │◄───┼── :5001
|
||||
│ └────────────────────────┬────────────────────────────┘ │
|
||||
│ │ TCP 3306 (internal only) │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ documize-db (mysql:8) │ │
|
||||
│ │ restart: unless-stopped │ │
|
||||
│ │ healthcheck: mysqladmin ping │ │
|
||||
│ └────────────────────────┬────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────▼──────┐ │
|
||||
│ │ db_data │ │
|
||||
│ │ volume │ │
|
||||
│ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Startup Sequence
|
||||
|
||||
1. `documize-init` starts → installs `curl` → downloads binary to `app_bin` volume → **exits 0**
|
||||
2. `documize-db` starts → MySQL initialises → healthcheck passes ✅
|
||||
3. `documize-app` starts (only after both conditions above are met) → exec's binary → serves on `:5001`
|
||||
|
||||
### Why the Init-Container Pattern?
|
||||
|
||||
Downloading a binary inside the main container's command creates a reboot loop: any failure causes Docker to restart the container, which retriggers the download. Separating the download into a `restart: "no"` init container means the main app container only ever does one thing — run the binary — with no network calls or failure modes on restart.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### File Layout
|
||||
|
||||
```
|
||||
documize-docker/
|
||||
├── docker-compose.yml # Stack definition — references ${VARS} from .env
|
||||
├── .env.template # Template — copy to .env and fill in secrets
|
||||
├── .env # Your secrets — NEVER commit this file
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### Step 1 — Set up your `.env` file
|
||||
|
||||
Copy the template and fill in all values marked ⚠️:
|
||||
|
||||
```bash
|
||||
cp .env.template .env
|
||||
```
|
||||
|
||||
### Step 2 — Environment Variable Reference
|
||||
|
||||
#### 🗄️ MySQL Database
|
||||
|
||||
| Variable | Default | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `MYSQL_DATABASE` | `documize` | Pre-filled | Database name created on first start |
|
||||
| `MYSQL_USER` | `documize` | Pre-filled | MySQL user Documize connects as |
|
||||
| `MYSQL_PASSWORD` | — | ⚠️ **Change** | Password for the `documize` MySQL user. Must match the DSN in `DOCUMIZEDB`. Generate: `openssl rand -base64 24` |
|
||||
| `MYSQL_ROOT_PASSWORD` | — | ⚠️ **Change** | MySQL root password for admin access. Not used by Documize. Generate: `openssl rand -base64 24` |
|
||||
|
||||
#### 🚀 Documize Application
|
||||
|
||||
| Variable | Default | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `DOCUMIZE_PORT` | `5001` | Pre-filled | Host port Documize is exposed on. Change the left side of the `ports` mapping. |
|
||||
| `DOCUMIZE_SALT` | — | ⚠️ **Change** | Password hashing salt. Must be ≥ 32 random characters. Generate: `openssl rand -hex 32`. **Set once — never change after first run.** |
|
||||
|
||||
#### 📧 SMTP Email (Optional)
|
||||
|
||||
All five SMTP variables must be set to enable email. Leave `SMTP_HOST` blank to disable.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `SMTP_HOST` | _(blank)_ | SMTP server hostname (e.g. `smtp.gmail.com`) |
|
||||
| `SMTP_PORT` | `587` | SMTP port — `587` for STARTTLS, `465` for SSL/TLS |
|
||||
| `SMTP_USER` | _(blank)_ | SMTP authentication username |
|
||||
| `SMTP_PASSWORD` | _(blank)_ | SMTP authentication password or app-specific password |
|
||||
| `SMTP_SENDER` | `documize@example.com` | From address on outgoing emails |
|
||||
|
||||
---
|
||||
|
||||
### MySQL Startup Flags
|
||||
|
||||
These flags are passed to MySQL 8 via the `command` key in `docker-compose.yml` and are **required** for Documize to function correctly. Do not remove them.
|
||||
|
||||
| Flag | Value | Purpose |
|
||||
|---|---|---|
|
||||
| `--character-set-server` | `utf8mb4` | Full Unicode support including emoji |
|
||||
| `--collation-server` | `utf8mb4_unicode_ci` | Case-insensitive Unicode collation |
|
||||
| `--ft-min-word-len` | `3` | Minimum word length for full-text search index — Documize requires exactly `3` |
|
||||
| `--innodb-file-per-table` | `1` | Each table in its own `.ibd` file — improves storage reclaim |
|
||||
| `--max-allowed-packet` | `256M` | Maximum packet size for large document imports |
|
||||
|
||||
---
|
||||
|
||||
### Changing the Host Port
|
||||
|
||||
Edit the `ports` mapping in `docker-compose.yml` **and** the `DOCUMIZE_PORT` value in `.env`:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
ports:
|
||||
- "${DOCUMIZE_PORT}:5001" # host:container
|
||||
```
|
||||
|
||||
```bash
|
||||
# .env
|
||||
DOCUMIZE_PORT=8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Named Volumes
|
||||
|
||||
| Volume | Mount path | Purpose |
|
||||
|---|---|---|
|
||||
| `db_data` | `/var/lib/mysql` | All MySQL data — content, users, settings. Never delete unless wiping everything. |
|
||||
| `app_bin` | `/app/bin` | The Documize binary. Safe to delete to force a re-download on next start. |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] `MYSQL_PASSWORD` changed from placeholder
|
||||
- [ ] `MYSQL_ROOT_PASSWORD` changed from placeholder
|
||||
- [ ] `DOCUMIZE_SALT` generated with `openssl rand -hex 32`
|
||||
- [ ] `.env` added to `.gitignore`
|
||||
- [ ] `.env` permissions set to `600` (`chmod 600 .env`)
|
||||
- [ ] MySQL `ports` mapping removed from `docker-compose.yml` (production only)
|
||||
- [ ] Documize placed behind a reverse proxy with TLS (production only)
|
||||
|
||||
### Remove the Database Port Mapping (Production)
|
||||
|
||||
The `db` service does not expose a port by default — MySQL is only reachable within the `documize_net` Docker network. If you added a port for local debugging, remove it before deploying:
|
||||
|
||||
```yaml
|
||||
# db service — remove or comment out for production:
|
||||
# ports:
|
||||
# - "3306:3306"
|
||||
```
|
||||
|
||||
### Reverse Proxy with TLS
|
||||
|
||||
Documize serves plain HTTP. In production, terminate TLS at a reverse proxy. Example **Caddy** config:
|
||||
|
||||
```
|
||||
docs.example.com {
|
||||
reverse_proxy localhost:5001
|
||||
}
|
||||
```
|
||||
|
||||
Example **Nginx** config:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name docs.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/docs.crt;
|
||||
ssl_certificate_key /etc/ssl/private/docs.key;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:5001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Day-to-Day Operations
|
||||
|
||||
### Start / Stop / Restart
|
||||
|
||||
```bash
|
||||
docker compose up -d # start all services (detached)
|
||||
docker compose stop # graceful stop — data preserved
|
||||
docker compose start # resume after stop
|
||||
docker compose restart # restart all services
|
||||
docker compose down # stop and remove containers (volumes kept)
|
||||
docker compose down -v # ⚠️ DESTRUCTIVE — removes containers AND volumes
|
||||
```
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
docker compose logs -f # all services, follow
|
||||
docker compose logs -f app # Documize app only
|
||||
docker compose logs -f db # MySQL only
|
||||
docker compose logs --tail=100 app # last 100 lines
|
||||
```
|
||||
|
||||
### Check Status
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Expected healthy state:
|
||||
|
||||
```
|
||||
NAME IMAGE STATUS
|
||||
documize-init alpine:3.19 Exited (0) ← correct: one-shot
|
||||
documize-db mysql:8 healthy
|
||||
documize-app alpine:3.19 running
|
||||
```
|
||||
|
||||
### Open a MySQL Shell
|
||||
|
||||
```bash
|
||||
docker exec -it documize-db mysql -u documize -p documize
|
||||
# enter MYSQL_PASSWORD when prompted
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💾 Backup & Restore
|
||||
|
||||
### Backup the Database
|
||||
|
||||
```bash
|
||||
docker exec documize-db \
|
||||
mysqldump -u documize -p"${MYSQL_PASSWORD}" documize \
|
||||
> documize-backup-$(date +%Y%m%d-%H%M%S).sql
|
||||
```
|
||||
|
||||
### Restore the Database
|
||||
|
||||
```bash
|
||||
docker exec -i documize-db \
|
||||
mysql -u documize -p"${MYSQL_PASSWORD}" documize \
|
||||
< documize-backup-20240101-120000.sql
|
||||
```
|
||||
|
||||
### Backup the Raw Volume (optional)
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-v documize_db_data:/data \
|
||||
-v $(pwd):/backup \
|
||||
alpine tar czf /backup/db_data-$(date +%Y%m%d).tar.gz -C /data .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⬆️ Upgrading Documize
|
||||
|
||||
> ⚠️ **Always back up the database before upgrading.**
|
||||
|
||||
```bash
|
||||
# 1. Back up
|
||||
docker exec documize-db mysqldump -u documize -p"${MYSQL_PASSWORD}" documize > pre-upgrade-backup.sql
|
||||
|
||||
# 2. Stop the stack
|
||||
docker compose down
|
||||
|
||||
# 3. Remove the cached binary to force re-download of the new version
|
||||
docker volume rm documize_app_bin
|
||||
|
||||
# 4. Update the download URL in docker-compose.yml (app-init command) to the new version
|
||||
|
||||
# 5. Start and watch for successful migration messages
|
||||
docker compose up -d
|
||||
docker compose logs -f app
|
||||
```
|
||||
|
||||
Documize runs database schema migrations automatically on startup.
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
| Symptom | Cause | Resolution |
|
||||
|---|---|---|
|
||||
| `app-init` exits with code `127` | `curl` not found — `apk` failed | Check internet connectivity and retry: `docker compose down -v && docker compose up -d` |
|
||||
| `app-init` exits with code `22` or `28` | Binary download failed | Check outbound HTTPS access to `community-downloads.s3.us-east-2.amazonaws.com` |
|
||||
| `documize-app` keeps restarting | MySQL not healthy or DSN mismatch | Run `docker compose logs db`. Verify `MYSQL_PASSWORD` in `.env` matches the password in `DOCUMIZEDB` |
|
||||
| Setup wizard shows DB connection error | DSN credentials mismatch | Ensure `MYSQL_PASSWORD` in `.env` is identical to the password in the `DOCUMIZEDB` connection string |
|
||||
| Port `5001` already in use | Port conflict on host | Change `DOCUMIZE_PORT` in `.env` and restart |
|
||||
| `app-init` exits with code `0` but binary won't run | Wrong CPU architecture | The default binary is `linux-amd64`. On ARM (e.g. Raspberry Pi, Apple Silicon Linux), replace the download URL with the `linux-arm64` binary |
|
||||
| Full-text search returns no results | MySQL FTS config missing | Confirm `--ft-min-word-len=3` is present in the `db` `command` block |
|
||||
| Lost `DOCUMIZE_SALT` — users locked out | Salt cannot be recovered | Restore from a database backup taken before the salt was changed |
|
||||
| Docker Compose variable not substituted | `.env` file missing or wrong path | Ensure `.env` is in the **same directory** as `docker-compose.yml` |
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Reference
|
||||
|
||||
```
|
||||
.
|
||||
├── docker-compose.yml # Stack definition — edit to change ports and resource limits
|
||||
├── .env.template # Commit this — safe template with no real secrets
|
||||
├── .env # Do NOT commit — your actual secrets
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
| Docker resource | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `documize-db` | Container | MySQL 8 database |
|
||||
| `documize-init` | Container | One-shot binary downloader |
|
||||
| `documize-app` | Container | Documize application |
|
||||
| `documize_net` | Network | Private bridge — only `app` can reach `db` |
|
||||
| `db_data` | Volume | MySQL data directory |
|
||||
| `app_bin` | Volume | Documize binary cache |
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Quick-Reference Commands
|
||||
|
||||
```bash
|
||||
# ── Setup ──────────────────────────────────────────────────────────────────────
|
||||
cp .env.template .env && chmod 600 .env # create secrets file
|
||||
openssl rand -hex 32 # generate DOCUMIZE_SALT
|
||||
openssl rand -base64 24 # generate a password
|
||||
|
||||
# ── Stack lifecycle ────────────────────────────────────────────────────────────
|
||||
docker compose up -d # start
|
||||
docker compose down # stop (data kept)
|
||||
docker compose down -v # ⚠️ wipe everything
|
||||
docker compose logs -f # live logs
|
||||
docker compose ps # status
|
||||
|
||||
# ── Database ───────────────────────────────────────────────────────────────────
|
||||
docker exec -it documize-db mysql -u documize -p documize # MySQL shell
|
||||
docker exec documize-db mysqldump -u documize -p"<pw>" documize > backup.sql
|
||||
|
||||
# ── Upgrades ───────────────────────────────────────────────────────────────────
|
||||
docker compose down && docker volume rm documize_app_bin && docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
Documize Community edition is licensed under the **GNU Affero General Public License v3 (AGPL-3.0)**.
|
||||
See [LICENSE](https://github.com/documize/community/blob/master/LICENSE) in the upstream repository.
|
||||
|
||||
This Docker configuration is provided as-is for self-hosting purposes.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
Made with ❤️ for the self-hosting community · [Documize upstream](https://github.com/documize/community) · [Report an issue](https://github.com/your-org/documize-docker/issues)
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,134 @@
|
||||
# ==============================================================================
|
||||
# Documize Community Edition v5.14.0 — Docker Compose
|
||||
# https://github.com/documize/community
|
||||
#
|
||||
# Stack:
|
||||
# - documize-init one-shot downloader (runs once, then exits cleanly)
|
||||
# - documize-app Alpine runner (executes the pre-downloaded binary)
|
||||
# - MySQL 8 self-hosted database (named volume for persistence)
|
||||
#
|
||||
# Quick-start:
|
||||
# 1. Copy .env.template to .env and fill in all ⚠️ values.
|
||||
# 2. Run: docker compose up -d
|
||||
# 3. Wait ~60 s, then open http://localhost:5001
|
||||
# 4. Complete the one-time setup wizard in your browser.
|
||||
#
|
||||
# See README.docx for full documentation.
|
||||
# ==============================================================================
|
||||
|
||||
services:
|
||||
|
||||
# ── MySQL 8 ──────────────────────────────────────────────────────────────────
|
||||
db:
|
||||
image: mysql:8
|
||||
container_name: documize-db
|
||||
restart: unless-stopped
|
||||
|
||||
mem_limit: 1g
|
||||
cpu_shares: 1024
|
||||
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "${MYSQL_USER}", "--password=${MYSQL_PASSWORD}"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 45s
|
||||
|
||||
environment:
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_USER: ${MYSQL_USER}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
|
||||
# utf8mb4 + full-text search flags required by Documize
|
||||
command: >
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_unicode_ci
|
||||
--ft-min-word-len=3
|
||||
--innodb-file-per-table=1
|
||||
--max-allowed-packet=256M
|
||||
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
|
||||
networks:
|
||||
- documize_net
|
||||
|
||||
# ── Init container: download binary once into shared volume ──────────────────
|
||||
app-init:
|
||||
image: alpine:3.19
|
||||
container_name: documize-init
|
||||
restart: "no" # runs once and exits — never restarts
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
set -e
|
||||
apk add --no-cache curl ca-certificates
|
||||
if [ ! -f /app/bin/documize ]; then
|
||||
echo 'Downloading Documize Community v5.14.0...'
|
||||
curl -fsSL -o /app/bin/documize https://community-downloads.s3.us-east-2.amazonaws.com/documize-community-linux-amd64
|
||||
chmod +x /app/bin/documize
|
||||
echo 'Download complete.'
|
||||
else
|
||||
echo 'Binary already present, skipping download.'
|
||||
fi
|
||||
|
||||
volumes:
|
||||
- app_bin:/app/bin
|
||||
|
||||
# ── App: run the pre-downloaded binary ───────────────────────────────────────
|
||||
app:
|
||||
image: alpine:3.19
|
||||
container_name: documize-app
|
||||
restart: unless-stopped
|
||||
|
||||
entrypoint: ["/app/bin/documize"]
|
||||
|
||||
depends_on:
|
||||
app-init:
|
||||
condition: service_completed_successfully
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
ports:
|
||||
- "${DOCUMIZE_PORT}:5001"
|
||||
|
||||
environment:
|
||||
# ── Network ────────────────────────────────────────────────────────────
|
||||
DOCUMIZEPORT: "${DOCUMIZE_PORT}"
|
||||
DOCUMIZELOCATION: selfhost
|
||||
|
||||
# ── Database (MySQL DSN) ────────────────────────────────────────────────
|
||||
DOCUMIZEDBTYPE: mysql
|
||||
DOCUMIZEDB: "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(db:3306)/${MYSQL_DATABASE}?charset=utf8mb4&parseTime=True&maxAllowedPacket=4194304"
|
||||
|
||||
# ── Security ────────────────────────────────────────────────────────────
|
||||
DOCUMIZESALT: "${DOCUMIZE_SALT}"
|
||||
|
||||
# ── SMTP (optional — only active when all SMTP_* vars are set) ──────────
|
||||
DOCUMIZESMTPHOST: "${SMTP_HOST}"
|
||||
DOCUMIZESMTPPORT: "${SMTP_PORT}"
|
||||
DOCUMIZESMTPUSER: "${SMTP_USER}"
|
||||
DOCUMIZESMTPPASSWORD: "${SMTP_PASSWORD}"
|
||||
DOCUMIZESMTPSENDER: "${SMTP_SENDER}"
|
||||
|
||||
volumes:
|
||||
- app_bin:/app/bin
|
||||
|
||||
networks:
|
||||
- documize_net
|
||||
|
||||
# ── Named volumes ─────────────────────────────────────────────────────────────
|
||||
volumes:
|
||||
db_data:
|
||||
driver: local # MySQL data — survives container restarts and upgrades
|
||||
app_bin:
|
||||
driver: local # Documize binary — downloaded once by app-init
|
||||
|
||||
# ── Internal network ──────────────────────────────────────────────────────────
|
||||
networks:
|
||||
documize_net:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,59 @@
|
||||
# ==============================================================================
|
||||
# Documize Community Edition v5.14.0 — Environment Variables
|
||||
# ==============================================================================
|
||||
#
|
||||
# USAGE:
|
||||
# 1. Copy this file: cp .env.template .env
|
||||
# 2. Fill in every value marked ⚠️
|
||||
# 3. NEVER commit .env to version control
|
||||
# 4. Add .env to your .gitignore: echo ".env" >> .gitignore
|
||||
#
|
||||
# All variables here are referenced in docker-compose.yml via ${VARIABLE_NAME}
|
||||
# ==============================================================================
|
||||
|
||||
# ── MySQL Database ─────────────────────────────────────────────────────────────
|
||||
|
||||
# Name of the database Documize will use (no need to change)
|
||||
MYSQL_DATABASE=documize
|
||||
|
||||
# MySQL user that Documize connects as (no need to change)
|
||||
MYSQL_USER=documize
|
||||
|
||||
# ⚠️ Password for the documize MySQL user
|
||||
# Rules: min 16 chars, mix of upper/lower/numbers/symbols
|
||||
# Used in BOTH the db service AND the DOCUMIZEDB connection string below
|
||||
# Generate: openssl rand -base64 24
|
||||
MYSQL_PASSWORD=xu05mFDSeCLTMJDjaAAUqr/7Fcv1fbEU
|
||||
|
||||
# ⚠️ Password for the MySQL root account (admin use only, not used by Documize)
|
||||
# Generate: openssl rand -base64 24
|
||||
MYSQL_ROOT_PASSWORD=xu05mFDSeCLTMJDjaAAUqr/7Fcv1fbEU
|
||||
|
||||
# ── Documize Application ───────────────────────────────────────────────────────
|
||||
|
||||
# Port Documize listens on (inside the container — matches docker-compose ports mapping)
|
||||
DOCUMIZE_PORT=5001
|
||||
|
||||
# ⚠️ Password hashing salt — must be a random string of at least 32 characters
|
||||
# Generate: openssl rand -hex 32
|
||||
# CRITICAL: Set this ONCE before the first run. Changing it later will
|
||||
# invalidate ALL existing user passwords and lock everyone out.
|
||||
DOCUMIZE_SALT=52c7337659a635c8541d097c78ac6b93851dabaa09d190337cf0a99265afe9f9
|
||||
|
||||
# ── SMTP Email Notifications (optional) ───────────────────────────────────────
|
||||
# Leave blank to disable email. All five values must be set to enable it.
|
||||
|
||||
# Hostname of your SMTP server (e.g. smtp.gmail.com, mail.example.com)
|
||||
SMTP_HOST=
|
||||
|
||||
# SMTP port — typically 587 (STARTTLS) or 465 (SSL/TLS)
|
||||
SMTP_PORT=587
|
||||
|
||||
# SMTP authentication username (usually your full email address)
|
||||
SMTP_USER=
|
||||
|
||||
# ⚠️ SMTP authentication password or app-specific password
|
||||
SMTP_PASSWORD=
|
||||
|
||||
# From address shown on outgoing Documize emails
|
||||
SMTP_SENDER=documize@example.com
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# ── Container user ───────────────────────────────────────────────
|
||||
# Match these to the UID/GID that owns the ./gitea host directory.
|
||||
# On Linux, run `id -u` and `id -g` to find yours.
|
||||
USER_UID=1000
|
||||
USER_GID=1000
|
||||
|
||||
# ── PostgreSQL database ──────────────────────────────────────────
|
||||
POSTGRES_USER=gitea
|
||||
POSTGRES_PASSWORD=change_me_to_a_strong_password
|
||||
POSTGRES_DB=gitea
|
||||
|
||||
# ── Host port mappings ───────────────────────────────────────────
|
||||
# host:container — change the host side if these ports are taken.
|
||||
GITEA_HTTP_HOST_PORT=3000
|
||||
GITEA_SSH_HOST_PORT=222
|
||||
|
||||
# ── Server settings ──────────────────────────────────────────────
|
||||
# For a purely local setup, localhost is fine.
|
||||
GITEA_DOMAIN=localhost
|
||||
GITEA_ROOT_URL=http://localhost:3000/
|
||||
|
||||
# ── Security secrets ─────────────────────────────────────────────
|
||||
# Generate each of these once and keep them stable. Do NOT change
|
||||
# SECRET_KEY after installation or encrypted data becomes unreadable.
|
||||
# docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret SECRET_KEY
|
||||
# docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret INTERNAL_TOKEN
|
||||
GITEA_SECRET_KEY=
|
||||
GITEA_INTERNAL_TOKEN=
|
||||
@@ -0,0 +1,95 @@
|
||||
# Local Gitea Deployment (Docker Compose)
|
||||
|
||||
A local, self-hosted [Gitea](https://docs.gitea.com/) instance running Gitea
|
||||
**1.27.3** with a **PostgreSQL 14** database, following the official Gitea
|
||||
Docker installation docs. All credentials, ports, and secrets live in the
|
||||
`.env` file rather than being hardcoded in the compose file.
|
||||
|
||||
## Files
|
||||
|
||||
- `docker-compose.yml` — service definitions for Gitea + PostgreSQL
|
||||
- `.env` — configuration (credentials, ports, secrets)
|
||||
- `README.md` — this file
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Engine with Compose v2 (`docker compose`, included in modern Docker).
|
||||
If needed, see the [Compose install instructions](https://docs.docker.com/compose/install/).
|
||||
|
||||
## Setup
|
||||
|
||||
Do the following before bringing the stack up.
|
||||
|
||||
### 1. Generate the security secrets
|
||||
|
||||
Run these two commands and paste each result into the matching empty field in
|
||||
`.env` (`GITEA_SECRET_KEY` and `GITEA_INTERNAL_TOKEN`):
|
||||
|
||||
```
|
||||
docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret SECRET_KEY
|
||||
docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret INTERNAL_TOKEN
|
||||
```
|
||||
|
||||
> **Do not change `SECRET_KEY` after the first install.** Encrypted data in the
|
||||
> database cannot be decrypted if you do.
|
||||
|
||||
### 2. Change the database password
|
||||
|
||||
Replace the placeholder `POSTGRES_PASSWORD` value in `.env` with a strong password.
|
||||
|
||||
### 3. Match the UID/GID
|
||||
|
||||
The setup uses a host volume (`./gitea`), so set `USER_UID` / `USER_GID` in
|
||||
`.env` to whoever owns that directory. On Linux:
|
||||
|
||||
```
|
||||
id -u # -> USER_UID
|
||||
id -g # -> USER_GID
|
||||
```
|
||||
|
||||
Wrong volume permissions are the most common reason the container won't start.
|
||||
|
||||
## Start
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Then open **http://localhost:3000** and finish setup in the browser wizard.
|
||||
|
||||
- If the wizard asks for the **database host**, use `db` (the service name), not `localhost`.
|
||||
- **SSH** is available on host port **222**.
|
||||
- Data persists in `./gitea` and `./postgres` next to the compose file.
|
||||
|
||||
## Common commands
|
||||
|
||||
```
|
||||
docker compose ps # check status
|
||||
docker compose logs # view logs
|
||||
docker compose down # stop and remove containers (volumes/data are kept)
|
||||
```
|
||||
|
||||
## Upgrading
|
||||
|
||||
```
|
||||
# Edit the image version in docker-compose.yml if you pin a specific release
|
||||
docker compose pull # pull new images
|
||||
docker compose up -d # recreate containers with the new images
|
||||
```
|
||||
|
||||
> Make sure your data is volumed outside the container (it is, via `./gitea`
|
||||
> and `./postgres`) before upgrading.
|
||||
|
||||
## Security note
|
||||
|
||||
Keep `.env` out of version control — it holds your secrets and database
|
||||
password. Add it to `.gitignore`:
|
||||
|
||||
```
|
||||
echo ".env" >> .gitignore
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
- [Gitea Docs — Installation with Docker](https://docs.gitea.com/installation/install-with-docker/)
|
||||
- [Gitea Docs — Managing deployments with environment variables](https://docs.gitea.com/installation/install-with-docker/#managing-deployments-with-environment-variables)
|
||||
@@ -0,0 +1,51 @@
|
||||
networks:
|
||||
gitea:
|
||||
external: false
|
||||
|
||||
services:
|
||||
server:
|
||||
image: docker.gitea.com/gitea:1.27.3
|
||||
container_name: gitea
|
||||
restart: always
|
||||
environment:
|
||||
# Container user (match to owner of the ./gitea host volume)
|
||||
- USER_UID=${USER_UID}
|
||||
- USER_GID=${USER_GID}
|
||||
# Database connection
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=db:5432
|
||||
- GITEA__database__NAME=${POSTGRES_DB}
|
||||
- GITEA__database__USER=${POSTGRES_USER}
|
||||
- GITEA__database__PASSWD=${POSTGRES_PASSWORD}
|
||||
# Server / general settings
|
||||
- GITEA__server__DOMAIN=${GITEA_DOMAIN}
|
||||
- GITEA__server__ROOT_URL=${GITEA_ROOT_URL}
|
||||
- GITEA__server__SSH_DOMAIN=${GITEA_DOMAIN}
|
||||
- GITEA__server__SSH_PORT=${GITEA_SSH_HOST_PORT}
|
||||
# Security secrets (generate these — see notes)
|
||||
- GITEA__security__SECRET_KEY=${GITEA_SECRET_KEY}
|
||||
- GITEA__security__INTERNAL_TOKEN=${GITEA_INTERNAL_TOKEN}
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- ./gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "${GITEA_HTTP_HOST_PORT}:3000"
|
||||
- "${GITEA_SSH_HOST_PORT}:22"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:14
|
||||
container_name: gitea-db
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
@@ -0,0 +1,15 @@
|
||||
#---------------------------------------------------------------------#
|
||||
# Homarr - A simple, yet powerful dashboard for your server. #
|
||||
#---------------------------------------------------------------------#
|
||||
services:
|
||||
homarr:
|
||||
container_name: homarr
|
||||
image: ghcr.io/homarr-labs/homarr:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=2eda5bfd55a3e8447a71a5b827eddd458976318f89d36d821a068769b8ef891b
|
||||
ports:
|
||||
- '7575:7575'
|
||||
@@ -0,0 +1,15 @@
|
||||
#---------------------------------------------------------------------#
|
||||
# Homarr - A simple, yet powerful dashboard for your server. #
|
||||
#---------------------------------------------------------------------#
|
||||
services:
|
||||
homarr:
|
||||
container_name: homarr
|
||||
image: ghcr.io/homarr-labs/homarr:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=2eda5bfd55a3e8447a71a5b827edff458976318f89d36d821a068769b8ef891b
|
||||
ports:
|
||||
- '7575:7575'
|
||||
@@ -0,0 +1,374 @@
|
||||
# 🎬 Jellyfin — Self-Hosted Media Server
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
## 📖 What is Jellyfin?
|
||||
|
||||
**Jellyfin** is a free, open-source media server that puts you in full control of your media library. It is the community-driven, privacy-respecting alternative to proprietary platforms like Plex and Emby — with **no subscriptions, no tracking, and no vendor lock-in**.
|
||||
|
||||
Jellyfin lets you collect, manage, and stream your movies, TV shows, music, live TV, photos, and books from your own server to any device, anywhere in the world.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Key Features & Capabilities
|
||||
|
||||
### 🎥 Media Management
|
||||
- **Movies & TV Shows** — Organizes your video library with rich metadata, posters, fan art, trailers, and ratings pulled from online databases (TMDb, TheTVDB, etc.)
|
||||
- **Music** — Full music library management with MusicBrainz integration, album art, lyrics, and playlists
|
||||
- **Photos** — Browse and share your photo collection; supports slideshow playback
|
||||
- **Books & Audiobooks** — Manage eBooks and audiobooks (via plugins)
|
||||
- **Live TV & DVR** — Watch and record live television using an HDHomeRun tuner or compatible TV backend (Tvheadend, NextPVR)
|
||||
|
||||
### 📡 Streaming & Playback
|
||||
- **Direct Play** — Streams media in its original format with zero quality loss if the client supports it
|
||||
- **Transcoding** — On-the-fly conversion of media to formats compatible with any client device
|
||||
- **Hardware Acceleration** — Supports Intel Quick Sync, AMD AMF, NVIDIA NVENC, VA-API, and VideoToolbox for fast, low-CPU transcoding
|
||||
- **Adaptive Bitrate** — Automatically adjusts quality based on your network connection
|
||||
- **Resume Playback** — Tracks your watch progress across all devices
|
||||
- **SyncPlay** — Watch content simultaneously with other users in perfect sync
|
||||
|
||||
### 👥 User Management
|
||||
- **Multiple Users** — Create separate profiles with individual libraries, permissions, and parental controls
|
||||
- **Parental Controls** — Restrict access by content rating, hide libraries, and set PIN-protected profiles
|
||||
- **Guest Access** — Share your server with friends and family without giving full access
|
||||
- **Activity Logs** — Full audit trail of who watched what and when
|
||||
|
||||
### 📺 Client Support
|
||||
Jellyfin has official and community clients for virtually every platform:
|
||||
- **Web Browser** — Built-in web UI accessible from any browser
|
||||
- **Android & Android TV**
|
||||
- **iOS & Apple TV**
|
||||
- **Roku**
|
||||
- **Fire TV / Kodi**
|
||||
- **Samsung Tizen & LG webOS Smart TVs**
|
||||
- **Desktop** — Windows, macOS, Linux
|
||||
- **Xbox** (via browser or Kodi)
|
||||
|
||||
### 🔌 DLNA & Casting
|
||||
- DLNA server support for smart TVs and media players
|
||||
- Chromecast support via the web client
|
||||
- AirPlay via community plugins
|
||||
|
||||
### 🔒 Privacy & Security
|
||||
- **100% self-hosted** — your data never leaves your server
|
||||
- **No telemetry** — zero tracking or analytics by default
|
||||
- **Optional SSL/HTTPS** — secure your server with Let's Encrypt or custom certificates
|
||||
- **API-first architecture** — full REST API for automation and integrations
|
||||
|
||||
### 🧩 Plugin Ecosystem
|
||||
Extend Jellyfin with community plugins:
|
||||
- Open Subtitles & Subscene for automatic subtitle downloads
|
||||
- Fanart.tv for enhanced artwork
|
||||
- Ani-Sync for AniList/MyAnimeList integration
|
||||
- Playback reporting & statistics dashboards
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following installed on your **Linux** host:
|
||||
|
||||
| Requirement | Minimum Version | Check Command |
|
||||
|---|---|---|
|
||||
| Docker Engine | 24.x+ | `docker --version` |
|
||||
| Docker Compose | v2.x+ (plugin) | `docker compose version` |
|
||||
| Available RAM | 2 GB+ | `free -h` |
|
||||
| Available Disk | Depends on library size | `df -h` |
|
||||
|
||||
> ⚠️ **Linux Only:** Running Jellyfin in Docker on Windows or macOS is **not officially supported** and hardware-accelerated transcoding will not work on those platforms. Install natively on Windows/macOS instead.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Recommended Directory Structure
|
||||
|
||||
Before deploying, set up the following folder layout on your host machine:
|
||||
|
||||
```
|
||||
jellyfin-deploy/
|
||||
├── docker-compose.yml ← The compose file from this repo
|
||||
├── jellyfin/
|
||||
│ ├── config/ ← Jellyfin configuration & database (auto-created)
|
||||
│ └── cache/ ← Transcoding cache (auto-created)
|
||||
└── media/
|
||||
├── movies/ ← Your movie files
|
||||
├── tvshows/ ← Your TV show files
|
||||
└── music/ ← Your music files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Installation Instructions
|
||||
|
||||
### Step 1 — Install Docker
|
||||
|
||||
If Docker is not already installed, follow the official guide for your distribution:
|
||||
|
||||
```bash
|
||||
# Ubuntu / Debian (quick install)
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
sudo usermod -aG docker $USER # Allow your user to run Docker without sudo
|
||||
newgrp docker # Apply group change without logging out
|
||||
```
|
||||
|
||||
Verify the installation:
|
||||
|
||||
```bash
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2 — Clone or Download This Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/jellyfin-docker.git
|
||||
cd jellyfin-docker
|
||||
```
|
||||
|
||||
Or simply create a working directory and place the provided files inside it:
|
||||
|
||||
```bash
|
||||
mkdir jellyfin-deploy && cd jellyfin-deploy
|
||||
# Copy docker-compose.yml and README.md here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3 — Create Required Directories
|
||||
|
||||
```bash
|
||||
mkdir -p jellyfin/config
|
||||
mkdir -p jellyfin/cache
|
||||
mkdir -p media/movies
|
||||
mkdir -p media/tvshows
|
||||
mkdir -p media/music
|
||||
```
|
||||
|
||||
> Your actual media files should be placed in (or symlinked from) the `media/` subdirectories, or you can update the bind mount paths in `docker-compose.yml` to point directly to where your media already lives.
|
||||
|
||||
---
|
||||
|
||||
### Step 4 — Configure `docker-compose.yml`
|
||||
|
||||
Open `docker-compose.yml` in a text editor and customize the following:
|
||||
|
||||
#### 4a. Set your Timezone
|
||||
```yaml
|
||||
environment:
|
||||
- TZ=America/New_York # ← Change to your timezone (e.g. Europe/London)
|
||||
```
|
||||
|
||||
Find your timezone string at: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
|
||||
#### 4b. Set the Published Server URL *(optional but recommended)*
|
||||
```yaml
|
||||
environment:
|
||||
- JELLYFIN_PublishedServerUrl=http://YOUR_SERVER_IP_OR_DOMAIN:8096
|
||||
```
|
||||
Replace `YOUR_SERVER_IP_OR_DOMAIN` with your server's LAN IP (e.g., `192.168.1.100`) or public domain name.
|
||||
|
||||
#### 4c. Set Media Paths
|
||||
By default, the compose file uses relative paths (`./media/movies`, etc.). To point at existing media folders elsewhere on your system, change the `source:` paths:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /mnt/nas/movies # ← Your actual path
|
||||
target: /media/movies
|
||||
read_only: true
|
||||
```
|
||||
|
||||
#### 4d. Set User/Group (Recommended)
|
||||
Find your current user's UID and GID:
|
||||
|
||||
```bash
|
||||
id
|
||||
# Example output: uid=1000(youruser) gid=1000(yourgroup)
|
||||
```
|
||||
|
||||
Uncomment and update the `user:` line in `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
user: "1000:1000"
|
||||
```
|
||||
|
||||
This prevents Jellyfin from running as root and ensures correct file permissions.
|
||||
|
||||
---
|
||||
|
||||
### Step 5 — Start Jellyfin
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The `-d` flag runs the container in the background (detached mode).
|
||||
|
||||
Check that it started successfully:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
docker compose logs -f jellyfin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 6 — Complete the Setup Wizard
|
||||
|
||||
Open your browser and navigate to:
|
||||
|
||||
```
|
||||
http://YOUR_SERVER_IP:8096
|
||||
```
|
||||
|
||||
You will be greeted by the **Jellyfin Setup Wizard**. Follow these steps:
|
||||
|
||||
1. **Create your admin account** — Set a username and password
|
||||
2. **Add media libraries** — Point Jellyfin to `/media/movies`, `/media/tvshows`, `/media/music` (these are the container-side paths)
|
||||
3. **Choose metadata language** — Select your preferred language for scraped info
|
||||
4. **Configure remote access** — Enable if you want to access Jellyfin outside your home network
|
||||
5. **Finish** — Jellyfin will begin scanning your media library immediately
|
||||
|
||||
---
|
||||
|
||||
## 🛑 Managing the Container
|
||||
|
||||
| Action | Command |
|
||||
|---|---|
|
||||
| Start | `docker compose up -d` |
|
||||
| Stop | `docker compose down` |
|
||||
| Restart | `docker compose restart jellyfin` |
|
||||
| View logs | `docker compose logs -f jellyfin` |
|
||||
| Update image | `docker compose pull && docker compose up -d` |
|
||||
| Remove container & volumes | `docker compose down -v` |
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Hardware Acceleration (Optional)
|
||||
|
||||
Hardware transcoding dramatically reduces CPU usage during video playback.
|
||||
|
||||
### Intel / AMD (VA-API via `/dev/dri`)
|
||||
|
||||
Uncomment the `devices` section in `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
```
|
||||
|
||||
Then in the Jellyfin Admin Dashboard:
|
||||
1. Go to **Dashboard → Playback → Transcoding**
|
||||
2. Set **Hardware acceleration** to **Video Acceleration API (VAAPI)**
|
||||
3. Set the VA-API device to `/dev/dri/renderD128`
|
||||
4. Save and test
|
||||
|
||||
### NVIDIA GPU
|
||||
|
||||
Requires the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html). Add to `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
||||
Then in Jellyfin, set Hardware acceleration to **NVENC**.
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Exposing Jellyfin to the Internet (Optional)
|
||||
|
||||
### Option A — Port Forwarding
|
||||
Forward TCP port `8096` on your router to your server's LAN IP.
|
||||
|
||||
### Option B — Reverse Proxy with HTTPS (Recommended)
|
||||
Use **Nginx Proxy Manager**, **Traefik**, or **Caddy** in front of Jellyfin to enable HTTPS. Example Nginx config snippet:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name jellyfin.yourdomain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8096;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 DLNA Configuration (Optional)
|
||||
|
||||
For DLNA support (smart TVs discovering Jellyfin automatically), you need host networking:
|
||||
|
||||
```yaml
|
||||
network_mode: host
|
||||
```
|
||||
|
||||
> ⚠️ When using host networking, remove the `ports:` section entirely — host network mode exposes all ports directly.
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Backup & Restore
|
||||
|
||||
The only directory you need to back up is `./jellyfin/config/`. This contains:
|
||||
- The Jellyfin database (`jellyfin.db`)
|
||||
- All server settings and user data
|
||||
- API keys and plugin configurations
|
||||
|
||||
```bash
|
||||
# Backup
|
||||
tar -czf jellyfin-backup-$(date +%Y%m%d).tar.gz ./jellyfin/config
|
||||
|
||||
# Restore
|
||||
tar -xzf jellyfin-backup-YYYYMMDD.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---|---|
|
||||
| Port 8096 not accessible | Check firewall: `sudo ufw allow 8096/tcp` |
|
||||
| Permission denied on media | Set correct `user: UID:GID` in compose file |
|
||||
| Media not appearing | Check that bind mount paths are correct; restart and re-scan |
|
||||
| High CPU during playback | Enable hardware acceleration; ensure Direct Play is used when possible |
|
||||
| Container won't start | Run `docker compose logs jellyfin` to inspect errors |
|
||||
|
||||
---
|
||||
|
||||
## 📚 Useful Links
|
||||
|
||||
- 🏠 [Jellyfin Official Website](https://jellyfin.org)
|
||||
- 📖 [Official Documentation](https://jellyfin.org/docs/)
|
||||
- 🐳 [Docker Hub Image](https://hub.docker.com/r/jellyfin/jellyfin)
|
||||
- 💬 [Jellyfin Forum](https://forum.jellyfin.org)
|
||||
- 🔌 [Plugin Catalogue](https://jellyfin.org/docs/general/server/plugins/)
|
||||
- ⚡ [Hardware Acceleration Guide](https://jellyfin.org/docs/general/post-install/transcoding/hardware-acceleration)
|
||||
- 🛡️ [Backup & Restore Guide](https://jellyfin.org/docs/general/administration/backup-and-restore)
|
||||
|
||||
---
|
||||
|
||||
## 📜 License
|
||||
|
||||
Jellyfin is released under the [GNU General Public License v2.0](https://github.com/jellyfin/jellyfin/blob/master/LICENSE).
|
||||
This deployment configuration is provided as-is for personal use.
|
||||
|
||||
---
|
||||
|
||||
*Made with ❤️ for the self-hosting community*
|
||||
@@ -0,0 +1,79 @@
|
||||
services:
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
# Optional: run as a specific user/group instead of root
|
||||
# Replace 1000:1000 with your actual UID:GID (run `id` in terminal to find yours)
|
||||
# user: "1000:1000"
|
||||
|
||||
ports:
|
||||
- 8096:8096/tcp # Main HTTP web UI & API
|
||||
- 8920:8920/tcp # HTTPS web UI (optional, requires cert setup)
|
||||
- 7359:7359/udp # Local network auto-discovery (DLNA)
|
||||
- 1900:1900/udp # DLNA service discovery (optional, requires host network)
|
||||
|
||||
volumes:
|
||||
# Jellyfin configuration & metadata database
|
||||
- ./jellyfin/config:/config
|
||||
|
||||
# Transcoding cache (can be tmpfs for better performance)
|
||||
- ./jellyfin/cache:/cache
|
||||
|
||||
# --- Media Libraries ---
|
||||
# Add as many bind mounts as you need for your media folders.
|
||||
# Set read_only: true for libraries Jellyfin should not modify.
|
||||
- type: bind
|
||||
source: ./media/movies
|
||||
target: /media/movies
|
||||
read_only: true
|
||||
|
||||
- type: bind
|
||||
source: ./media/tvshows
|
||||
target: /media/tvshows
|
||||
read_only: true
|
||||
|
||||
- type: bind
|
||||
source: ./media/music
|
||||
target: /media/music
|
||||
read_only: true
|
||||
|
||||
# Optional: custom fonts for subtitle burn-in during transcoding
|
||||
# - type: bind
|
||||
# source: ./fonts
|
||||
# target: /usr/local/share/fonts/custom
|
||||
# read_only: true
|
||||
|
||||
# Optional: fallback fonts directory
|
||||
# (set fallback font path to /fallback_fonts in Jellyfin server settings)
|
||||
# - type: bind
|
||||
# source: ./fallback_fonts
|
||||
# target: /fallback_fonts
|
||||
# read_only: true
|
||||
|
||||
environment:
|
||||
# Optional: set this to your server's public URL for correct autodiscovery
|
||||
- JELLYFIN_PublishedServerUrl=http://tube.martinhal.tech:8096
|
||||
|
||||
# Timezone (change to your local timezone)
|
||||
- TZ=Europe/Lisbon
|
||||
|
||||
# Optional: enable GPU hardware acceleration (Intel/AMD iGPU via /dev/dri)
|
||||
# Uncomment the section below if your host has a compatible GPU
|
||||
# devices:
|
||||
# - /dev/dri:/dev/dri
|
||||
|
||||
# Required if using host network mode for full DLNA support
|
||||
# network_mode: host
|
||||
|
||||
# Needed for Docker healthcheck to pass in host network mode
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
restart: unless-stopped
|
||||
|
||||
# Optional: resource limits to prevent Jellyfin from consuming all system resources
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# memory: 4G
|
||||
# cpus: "4.0"
|
||||
@@ -0,0 +1,51 @@
|
||||
###############################################################################
|
||||
# NetBox — all secrets & tunable configuration
|
||||
#
|
||||
# Docker Compose auto-loads this file (must be named ".env" and sit next to
|
||||
# docker-compose.yml). Keep it OUT of version control: echo ".env" >> .gitignore
|
||||
###############################################################################
|
||||
|
||||
# ── Image version ───────────────────────────────────────────────────────────
|
||||
# Keep the NetBox Docker image tag in sync with the compose file you deploy.
|
||||
VERSION=v4.6-5.0.2
|
||||
|
||||
# ── Web exposure ────────────────────────────────────────────────────────────
|
||||
# Host port that maps to NetBox's internal 8080. Change if 8000 is taken.
|
||||
NETBOX_PORT=8000
|
||||
# Comma-separated hostnames/IPs allowed to reach NetBox. Tighten in production,
|
||||
# e.g. ALLOWED_HOSTS=netbox.example.com
|
||||
ALLOWED_HOSTS=*
|
||||
|
||||
# ── Core secrets (generated — do not reuse elsewhere) ───────────────────────
|
||||
SECRET_KEY=2HDStlQcTKmJyE_Wvf7ItgRmXet4sxdK_z9cw42_kShXnCjioRUltX2dTt0mAK2Z0yI
|
||||
API_TOKEN_PEPPER_1=t5t-KOSYfm3gqPsYJzwiefcOsRmIwxNce7MMUDmXc-qWG96mNVyfvugE_dcH1J_wlaA
|
||||
|
||||
# ── PostgreSQL ──────────────────────────────────────────────────────────────
|
||||
DB_NAME=netbox
|
||||
DB_USER=netbox
|
||||
DB_PASSWORD=6ihrE0FOUH3HVs4BSnEW8IT4
|
||||
|
||||
# ── Redis / Valkey (two separate instances need two separate passwords) ──────
|
||||
REDIS_PASSWORD=jArceFdnYgevnBPyevdjMpna
|
||||
REDIS_CACHE_PASSWORD=0T2NXivbsaulVrQrjI9otiQP
|
||||
|
||||
# ── Email / SMTP — PurelyMail ───────────────────────────────────────────────
|
||||
# smtp.purelymail.com : 465 over SSL/TLS.
|
||||
# EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive — do NOT set both true.
|
||||
EMAIL_SERVER=smtp.purelymail.com
|
||||
EMAIL_PORT=465
|
||||
EMAIL_USE_SSL=true
|
||||
EMAIL_USE_TLS=false
|
||||
EMAIL_USERNAME=you@yourdomain.com
|
||||
EMAIL_PASSWORD=CHANGE_ME_purelymail_password
|
||||
EMAIL_FROM=you@yourdomain.com
|
||||
EMAIL_TIMEOUT=10
|
||||
|
||||
# ── Initial superuser ───────────────────────────────────────────────────────
|
||||
# Set SKIP_SUPERUSER=false to have the container create this account on first
|
||||
# boot. Change the password/token before deploying, then set back to true.
|
||||
SKIP_SUPERUSER=false
|
||||
SUPERUSER_NAME=admin
|
||||
SUPERUSER_EMAIL=admin@yourdomain.com
|
||||
SUPERUSER_PASSWORD=CHANGE_ME_admin_password
|
||||
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
||||
@@ -0,0 +1,144 @@
|
||||
services:
|
||||
netbox: &netbox
|
||||
image: docker.io/netboxcommunity/netbox:${VERSION:-v4.6-5.0.2}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
redis-cache:
|
||||
condition: service_healthy
|
||||
user: "netbox:root"
|
||||
ports:
|
||||
- "${NETBOX_PORT:-8000}:8080"
|
||||
environment:
|
||||
# Core
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
API_TOKEN_PEPPER_1: ${API_TOKEN_PEPPER_1}
|
||||
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-*}
|
||||
# Database
|
||||
DB_HOST: postgres
|
||||
DB_NAME: ${DB_NAME:-netbox}
|
||||
DB_USER: ${DB_USER:-netbox}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
# Redis — task queue
|
||||
REDIS_HOST: redis
|
||||
REDIS_DATABASE: "0"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
REDIS_SSL: "false"
|
||||
REDIS_INSECURE_SKIP_TLS_VERIFY: "false"
|
||||
# Redis — cache
|
||||
REDIS_CACHE_HOST: redis-cache
|
||||
REDIS_CACHE_DATABASE: "1"
|
||||
REDIS_CACHE_PASSWORD: ${REDIS_CACHE_PASSWORD}
|
||||
REDIS_CACHE_SSL: "false"
|
||||
REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY: "false"
|
||||
# Email / SMTP (PurelyMail)
|
||||
EMAIL_SERVER: ${EMAIL_SERVER:-smtp.purelymail.com}
|
||||
EMAIL_PORT: ${EMAIL_PORT:-465}
|
||||
EMAIL_USE_SSL: ${EMAIL_USE_SSL:-true}
|
||||
EMAIL_USE_TLS: ${EMAIL_USE_TLS:-false}
|
||||
EMAIL_USERNAME: ${EMAIL_USERNAME}
|
||||
EMAIL_PASSWORD: ${EMAIL_PASSWORD}
|
||||
EMAIL_FROM: ${EMAIL_FROM}
|
||||
EMAIL_TIMEOUT: ${EMAIL_TIMEOUT:-10}
|
||||
EMAIL_SSL_CERTFILE: ""
|
||||
EMAIL_SSL_KEYFILE: ""
|
||||
# Initial superuser
|
||||
SKIP_SUPERUSER: ${SKIP_SUPERUSER:-true}
|
||||
SUPERUSER_NAME: ${SUPERUSER_NAME:-admin}
|
||||
SUPERUSER_EMAIL: ${SUPERUSER_EMAIL:-admin@example.com}
|
||||
SUPERUSER_PASSWORD: ${SUPERUSER_PASSWORD:-admin}
|
||||
SUPERUSER_API_TOKEN: ${SUPERUSER_API_TOKEN:-}
|
||||
# Misc
|
||||
GRAPHQL_ENABLED: "true"
|
||||
WEBHOOKS_ENABLED: "true"
|
||||
METRICS_ENABLED: "false"
|
||||
MEDIA_ROOT: /opt/netbox/netbox/media
|
||||
RELEASE_CHECK_URL: https://api.github.com/repos/netbox-community/netbox/releases
|
||||
healthcheck:
|
||||
test: curl -f http://localhost:8080/login/ || exit 1
|
||||
start_period: 90s
|
||||
timeout: 3s
|
||||
interval: 15s
|
||||
volumes:
|
||||
- netbox-media-files:/opt/netbox/netbox/media:rw
|
||||
- netbox-reports-files:/opt/netbox/netbox/reports:rw
|
||||
- netbox-scripts-files:/opt/netbox/netbox/scripts:rw
|
||||
|
||||
netbox-worker:
|
||||
<<: *netbox
|
||||
ports: []
|
||||
depends_on:
|
||||
netbox:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- /opt/netbox/netbox/manage.py
|
||||
- rqworker
|
||||
healthcheck:
|
||||
test: ps -aux | grep -v grep | grep -q rqworker || exit 1
|
||||
start_period: 20s
|
||||
timeout: 3s
|
||||
interval: 15s
|
||||
|
||||
# postgres
|
||||
postgres:
|
||||
image: docker.io/postgres:18-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME:-netbox}
|
||||
POSTGRES_USER: ${DB_USER:-netbox}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
healthcheck:
|
||||
test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER
|
||||
start_period: 20s
|
||||
timeout: 30s
|
||||
interval: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- netbox-postgres:/var/lib/postgresql
|
||||
|
||||
# redis — task queue (persistent)
|
||||
redis:
|
||||
image: docker.io/valkey/valkey:9.1-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
||||
- valkey-server --appendonly yes --requirepass $$REDIS_PASSWORD
|
||||
environment:
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
healthcheck: &redis-healthcheck
|
||||
test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]'
|
||||
start_period: 5s
|
||||
timeout: 3s
|
||||
interval: 1s
|
||||
retries: 5
|
||||
volumes:
|
||||
- netbox-redis-data:/data
|
||||
|
||||
# redis-cache — transient cache
|
||||
redis-cache:
|
||||
image: docker.io/valkey/valkey:9.1-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
||||
- valkey-server --requirepass $$REDIS_PASSWORD
|
||||
environment:
|
||||
REDIS_PASSWORD: ${REDIS_CACHE_PASSWORD}
|
||||
healthcheck: *redis-healthcheck
|
||||
volumes:
|
||||
- netbox-redis-cache-data:/data
|
||||
|
||||
volumes:
|
||||
netbox-media-files:
|
||||
driver: local
|
||||
netbox-postgres:
|
||||
driver: local
|
||||
netbox-redis-cache-data:
|
||||
driver: local
|
||||
netbox-redis-data:
|
||||
driver: local
|
||||
netbox-reports-files:
|
||||
driver: local
|
||||
netbox-scripts-files:
|
||||
driver: local
|
||||
Binary file not shown.
@@ -0,0 +1,302 @@
|
||||
# ==============================================================================
|
||||
# OpenProject — Docker Compose (stable/17)
|
||||
# With SMTP outbound-email support
|
||||
# ==============================================================================
|
||||
# Based on:
|
||||
# https://www.openproject.org/docs/installation-and-operations/installation/docker-compose/
|
||||
# https://github.com/opf/openproject-docker-compose (branch: stable/17)
|
||||
# https://www.openproject.org/docs/installation-and-operations/configuration/outbound-emails/
|
||||
# https://www.openproject.org/docs/installation-and-operations/configuration/environment/
|
||||
#
|
||||
# Usage:
|
||||
# 1. cp .env.example .env # then edit .env with your real values
|
||||
# 2. sudo mkdir -p /var/openproject/assets
|
||||
# 3. sudo chown 1000:1000 -R /var/openproject/assets
|
||||
# 4. docker compose up -d --build --pull always
|
||||
#
|
||||
# SMTP variables live in the .env file (see .env.example).
|
||||
# They are injected into every OpenProject container via the x-op-app anchor.
|
||||
# ==============================================================================
|
||||
|
||||
version: "3.7"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Networks – frontend faces the proxy; backend is DB / cache only.
|
||||
# ---------------------------------------------------------------------------
|
||||
networks:
|
||||
frontend:
|
||||
backend:
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Volumes – persisted between restarts / upgrades.
|
||||
# pgdata – PostgreSQL WAL + tables
|
||||
# opdata – uploaded attachments & assets
|
||||
# ---------------------------------------------------------------------------
|
||||
volumes:
|
||||
pgdata:
|
||||
opdata:
|
||||
|
||||
# ===========================================================================
|
||||
# YAML Anchors – shared restart policy, image tag, and environment block.
|
||||
# ===========================================================================
|
||||
|
||||
x-op-restart-policy: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-op-image: &image
|
||||
image: openproject/openproject:${TAG:-17-slim}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# x-op-app – merged into every OpenProject container.
|
||||
# All SMTP_* variables are pulled from the .env file so that secrets are
|
||||
# never hard-coded in this file. See .env.example for every placeholder.
|
||||
# ---------------------------------------------------------------------------
|
||||
x-op-app: &app
|
||||
<<: [*image, *restart_policy]
|
||||
environment:
|
||||
# --- Core OpenProject ------------------------------------------------
|
||||
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}"
|
||||
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME:-localhost:8080}"
|
||||
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"
|
||||
OPENPROJECT_EDITION: "${OPENPROJECT_EDITION:-standard}"
|
||||
|
||||
# Allow hocuspocus to reach the web container by its service name
|
||||
OPENPROJECT_ADDITIONAL__HOST__NAMES: "${OPENPROJECT_ADDITIONAL__HOST__NAMES:-web}"
|
||||
|
||||
# --- Database ---------------------------------------------------------
|
||||
DATABASE_URL: "${DATABASE_URL:-postgres://postgres:${POSTGRES_PASSWORD:-p4ssw0rd}@db/openproject?pool=20&encoding=unicode&reconnect=true}"
|
||||
|
||||
# --- Cache ------------------------------------------------------------
|
||||
OPENPROJECT_CACHE__MEMCACHE__SERVER: "cache:11211"
|
||||
OPENPROJECT_RAILS__CACHE__STORE: "memcache"
|
||||
|
||||
# --- Threads ----------------------------------------------------------
|
||||
RAILS_MIN_THREADS: "${RAILS_MIN_THREADS:-4}"
|
||||
RAILS_MAX_THREADS: "${RAILS_MAX_THREADS:-16}"
|
||||
|
||||
# --- Collaborative editing (Hocuspocus) ------------------------------
|
||||
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL: "${COLLABORATIVE_SERVER_URL:-wss://${OPENPROJECT_HOST__NAME}/hocuspocus}"
|
||||
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET: "${COLLABORATIVE_SERVER_SECRET:-OVERRIDE_ME_PLEASE}"
|
||||
|
||||
# --- Inbound email (IMAP) – disabled by default -----------------------
|
||||
IMAP_ENABLED: "${IMAP_ENABLED:-false}"
|
||||
|
||||
# ================================================================
|
||||
# SMTP – Outbound e-mail configuration
|
||||
# ================================================================
|
||||
EMAIL_DELIVERY_METHOD: "${EMAIL_DELIVERY_METHOD:-smtp}"
|
||||
SMTP_ADDRESS: "${SMTP_ADDRESS}"
|
||||
SMTP_PORT: "${SMTP_PORT:-587}"
|
||||
SMTP_DOMAIN: "${SMTP_DOMAIN}"
|
||||
SMTP_AUTHENTICATION: "${SMTP_AUTHENTICATION:-plain}"
|
||||
SMTP_USER_NAME: "${SMTP_USER_NAME}"
|
||||
SMTP_PASSWORD: "${SMTP_PASSWORD}"
|
||||
SMTP_ENABLE_STARTTLS_AUTO: "${SMTP_ENABLE_STARTTLS_AUTO:-true}"
|
||||
SMTP_SSL: "${SMTP_SSL:-false}"
|
||||
SMTP_TIMEOUT: "${SMTP_TIMEOUT:-5}"
|
||||
|
||||
OPENPROJECT_MAILER__FROM__ADDRESS: "${MAILER_FROM_ADDRESS:-openproject@example.com}"
|
||||
|
||||
volumes:
|
||||
- "${OPDATA:-opdata}:/var/openproject/assets"
|
||||
|
||||
# ===========================================================================
|
||||
# Services
|
||||
# ===========================================================================
|
||||
|
||||
services:
|
||||
# -----------------------------------------------------------------------
|
||||
# db – PostgreSQL 16
|
||||
# -----------------------------------------------------------------------
|
||||
db:
|
||||
<<: *restart_policy
|
||||
image: postgres:16-alpine
|
||||
networks:
|
||||
- backend
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-p4ssw0rd}"
|
||||
POSTGRES_DB: openproject
|
||||
POSTGRES_USER: postgres
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d openproject"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# cache – Memcached
|
||||
# -----------------------------------------------------------------------
|
||||
cache:
|
||||
<<: *restart_policy
|
||||
image: memcached:alpine
|
||||
networks:
|
||||
- backend
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# seeder – one-shot container
|
||||
#
|
||||
# FIX: Must NOT inherit the restart policy from x-op-app. The seeder is a
|
||||
# one-shot job that exits with code 0 on success. If restart: unless-stopped
|
||||
# is in effect Docker will keep restarting it and it will appear permanently
|
||||
# "waiting" to dependent services that expect service_completed_successfully.
|
||||
#
|
||||
# We use the *image anchor only (no *restart_policy) and set
|
||||
# restart: "no" explicitly.
|
||||
# -----------------------------------------------------------------------
|
||||
seeder:
|
||||
<<: *image
|
||||
restart: "no"
|
||||
environment:
|
||||
# Minimal env required for seeder (DB + cache)
|
||||
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}"
|
||||
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME:-localhost:8080}"
|
||||
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"
|
||||
OPENPROJECT_EDITION: "${OPENPROJECT_EDITION:-standard}"
|
||||
DATABASE_URL: "${DATABASE_URL:-postgres://postgres:${POSTGRES_PASSWORD:-p4ssw0rd}@db/openproject?pool=20&encoding=unicode&reconnect=true}"
|
||||
OPENPROJECT_CACHE__MEMCACHE__SERVER: "cache:11211"
|
||||
OPENPROJECT_RAILS__CACHE__STORE: "memcache"
|
||||
RAILS_MIN_THREADS: "${RAILS_MIN_THREADS:-4}"
|
||||
RAILS_MAX_THREADS: "${RAILS_MAX_THREADS:-16}"
|
||||
volumes:
|
||||
- "${OPDATA:-opdata}:/var/openproject/assets"
|
||||
networks:
|
||||
- backend
|
||||
command: ["./docker/prod/seeder"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_started
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# web – Puma application server
|
||||
#
|
||||
# FIX: healthcheck timings tightened to match official stable/17 repo
|
||||
# (interval 10s / timeout 3s / retries 3 / start_period 30s).
|
||||
# The previous values (30s/5s/5/60s) caused downstream containers that
|
||||
# depend on service_healthy to wait far too long, making them appear stuck.
|
||||
# -----------------------------------------------------------------------
|
||||
web:
|
||||
<<: *app
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
command: ["./docker/prod/web"]
|
||||
hostname: "${OPENPROJECT_HOST__NAME:-localhost:8080}"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_started
|
||||
seeder:
|
||||
condition: service_completed_successfully
|
||||
labels:
|
||||
- autoheal=true
|
||||
healthcheck:
|
||||
test:
|
||||
- "CMD"
|
||||
- "curl"
|
||||
- "-f"
|
||||
- "http://localhost:8080${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}/health_checks/default"
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
expose:
|
||||
- "8080"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# autoheal – automatically restarts unhealthy containers
|
||||
# FIX: Added from official stable/17 compose. Without autoheal, an
|
||||
# unhealthy web container (e.g. stuck in a loop) is never restarted even
|
||||
# though Docker marks it unhealthy, which can block the seeder indirectly.
|
||||
# -----------------------------------------------------------------------
|
||||
autoheal:
|
||||
image: willfarrell/autoheal:1.2.0
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
environment:
|
||||
AUTOHEAL_CONTAINER_LABEL: autoheal
|
||||
AUTOHEAL_START_PERIOD: 600
|
||||
AUTOHEAL_INTERVAL: 30
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# worker – Active Job background processor
|
||||
# -----------------------------------------------------------------------
|
||||
worker:
|
||||
<<: *app
|
||||
networks:
|
||||
- frontend # needs outbound access for SMTP
|
||||
- backend
|
||||
command: ["./docker/prod/worker"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_started
|
||||
seeder:
|
||||
condition: service_completed_successfully
|
||||
# Explicit DNS – prevents "Network is unreachable" when the container
|
||||
# tries to connect to an external SMTP server. See OP#44515.
|
||||
dns:
|
||||
- "8.8.8.8"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# cron – periodic background tasks (e.g. sending digest emails)
|
||||
# FIX: Added from official stable/17 compose. Missing cron can prevent
|
||||
# certain background jobs from running, which can mask seeder-related
|
||||
# issues or cause incomplete initialisation.
|
||||
# -----------------------------------------------------------------------
|
||||
cron:
|
||||
<<: *app
|
||||
networks:
|
||||
- backend
|
||||
command: ["./docker/prod/cron"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_started
|
||||
seeder:
|
||||
condition: service_completed_successfully
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# proxy – Caddy reverse proxy
|
||||
# -----------------------------------------------------------------------
|
||||
proxy:
|
||||
<<: *restart_policy
|
||||
image: openproject/openproject:${TAG:-17-slim}
|
||||
networks:
|
||||
- frontend
|
||||
ports:
|
||||
- "${PORT:-8080}:80"
|
||||
volumes:
|
||||
- "${OPDATA:-opdata}:/var/openproject/assets"
|
||||
command: ["./docker/prod/proxy"]
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}"
|
||||
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME:-localhost:8080}"
|
||||
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# hocuspocus – WebSocket collaboration server
|
||||
# -----------------------------------------------------------------------
|
||||
hocuspocus:
|
||||
<<: *restart_policy
|
||||
image: openproject/openproject:${TAG:-17-slim}
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
command: ["./docker/prod/hocuspocus"]
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET: "${COLLABORATIVE_SERVER_SECRET:-OVERRIDE_ME_PLEASE}"
|
||||
expose:
|
||||
- "3000"
|
||||
@@ -0,0 +1,280 @@
|
||||
# ==============================================================================
|
||||
# OpenProject — Docker Compose (stable/17)
|
||||
# With SMTP outbound-email support
|
||||
# ==============================================================================
|
||||
# Based on:
|
||||
# https://www.openproject.org/docs/installation-and-operations/installation/docker-compose/
|
||||
# https://github.com/opf/openproject-docker-compose (branch: stable/17)
|
||||
# https://www.openproject.org/docs/installation-and-operations/configuration/outbound-emails/
|
||||
# https://www.openproject.org/docs/installation-and-operations/configuration/environment/
|
||||
#
|
||||
# Usage:
|
||||
# 1. cp .env.example .env # then edit .env with your real values
|
||||
# 2. sudo mkdir -p /var/openproject/assets
|
||||
# 3. sudo chown 1000:1000 -R /var/openproject/assets
|
||||
# 4. docker compose up -d --build --pull always
|
||||
#
|
||||
# SMTP variables live in the .env file (see .env.example).
|
||||
# They are injected into every OpenProject container via the x-op-app anchor.
|
||||
# ==============================================================================
|
||||
|
||||
version: "3.7"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Networks – frontend faces the proxy; backend is DB / cache only.
|
||||
# ---------------------------------------------------------------------------
|
||||
networks:
|
||||
frontend:
|
||||
backend:
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Volumes – persisted between restarts / upgrades.
|
||||
# pgdata – PostgreSQL WAL + tables
|
||||
# opdata – uploaded attachments & assets
|
||||
# ---------------------------------------------------------------------------
|
||||
volumes:
|
||||
pgdata:
|
||||
opdata:
|
||||
|
||||
# ===========================================================================
|
||||
# YAML Anchors – shared restart policy, image tag, and environment block.
|
||||
# ===========================================================================
|
||||
|
||||
x-op-restart-policy: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-op-image: &image
|
||||
image: openproject/openproject:${TAG:-17-slim}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# x-op-app – merged into every OpenProject container.
|
||||
# All SMTP_* variables are pulled from the .env file so that secrets are
|
||||
# never hard-coded in this file. See .env.example for every placeholder.
|
||||
# ---------------------------------------------------------------------------
|
||||
x-op-app: &app
|
||||
<<: [*image, *restart_policy]
|
||||
environment:
|
||||
# --- Core OpenProject ------------------------------------------------
|
||||
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}"
|
||||
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME:-localhost:8080}"
|
||||
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"
|
||||
OPENPROJECT_EDITION: "${OPENPROJECT_EDITION:-standard}"
|
||||
|
||||
# --- Database ---------------------------------------------------------
|
||||
DATABASE_URL: "${DATABASE_URL:-postgres://postgres:${POSTGRES_PASSWORD:-p4ssw0rd}@db/openproject?pool=20&encoding=unicode&reconnect=true}"
|
||||
|
||||
# --- Cache ------------------------------------------------------------
|
||||
OPENPROJECT_CACHE__MEMCACHE__SERVER: "cache:11211"
|
||||
OPENPROJECT_RAILS__CACHE__STORE: "memcache"
|
||||
|
||||
# --- Threads ----------------------------------------------------------
|
||||
RAILS_MIN_THREADS: "${RAILS_MIN_THREADS:-4}"
|
||||
RAILS_MAX_THREADS: "${RAILS_MAX_THREADS:-16}"
|
||||
|
||||
# --- Collaborative editing (Hocuspocus) ------------------------------
|
||||
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL: "${COLLABORATIVE_SERVER_URL:-wss://${OPENPROJECT_HOST__NAME}/hocuspocus}"
|
||||
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET: "${COLLABORATIVE_SERVER_SECRET:-OVERRIDE_ME_PLEASE}"
|
||||
|
||||
# --- Inbound email (IMAP) – disabled by default -----------------------
|
||||
IMAP_ENABLED: "${IMAP_ENABLED:-false}"
|
||||
|
||||
# ================================================================
|
||||
# SMTP – Outbound e-mail configuration
|
||||
# ================================================================
|
||||
# Every variable below maps to an OpenProject environment variable
|
||||
# that is documented at:
|
||||
# /docs/installation-and-operations/configuration/outbound-emails/
|
||||
# /docs/installation-and-operations/configuration/environment/
|
||||
#
|
||||
# Setting these via environment variables **disables** the matching
|
||||
# form in Administration → Emails and notifications (by design).
|
||||
#
|
||||
# Common SMTP_PORT values:
|
||||
# 587 – submission with STARTTLS (most providers, recommended)
|
||||
# 465 – implicit SSL/TLS
|
||||
# 25 – unencrypted (never use in production)
|
||||
#
|
||||
# Common SMTP_AUTHENTICATION values:
|
||||
# plain – most cloud providers (Gmail, Outlook, SendGrid …)
|
||||
# login – some legacy / on-premises servers
|
||||
# cram_md5
|
||||
#
|
||||
# For SendGrid specifically:
|
||||
# SMTP_USER_NAME=apikey
|
||||
# SMTP_PASSWORD=<your-sendgrid-api-key>
|
||||
# ================================================================
|
||||
EMAIL_DELIVERY_METHOD: "${EMAIL_DELIVERY_METHOD:-smtp}"
|
||||
SMTP_ADDRESS: "${SMTP_ADDRESS}"
|
||||
SMTP_PORT: "${SMTP_PORT:-587}"
|
||||
SMTP_DOMAIN: "${SMTP_DOMAIN}"
|
||||
SMTP_AUTHENTICATION: "${SMTP_AUTHENTICATION:-plain}"
|
||||
SMTP_USER_NAME: "${SMTP_USER_NAME}"
|
||||
SMTP_PASSWORD: "${SMTP_PASSWORD}"
|
||||
SMTP_ENABLE_STARTTLS_AUTO: "${SMTP_ENABLE_STARTTLS_AUTO:-true}"
|
||||
SMTP_SSL: "${SMTP_SSL:-false}"
|
||||
SMTP_TIMEOUT: "${SMTP_TIMEOUT:-5}"
|
||||
|
||||
# Envelope sender – the "From" address that appears in every mail.
|
||||
# Must be a valid address on your SMTP account unless your provider
|
||||
# allows arbitrary senders.
|
||||
OPENPROJECT_MAILER__FROM__ADDRESS: "${MAILER_FROM_ADDRESS:-openproject@example.com}"
|
||||
|
||||
volumes:
|
||||
- "${OPDATA:-opdata}:/var/openproject/assets"
|
||||
|
||||
# ===========================================================================
|
||||
# Services
|
||||
# ===========================================================================
|
||||
|
||||
services:
|
||||
# -----------------------------------------------------------------------
|
||||
# db – PostgreSQL 16
|
||||
# Stores all application data. The named volume pgdata persists the
|
||||
# data directory so it survives container re-creations.
|
||||
# -----------------------------------------------------------------------
|
||||
db:
|
||||
<<: *restart_policy
|
||||
image: postgres:16-alpine
|
||||
networks:
|
||||
- backend
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-p4ssw0rd}"
|
||||
POSTGRES_DB: openproject
|
||||
POSTGRES_USER: postgres
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d openproject"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# cache – Memcached
|
||||
# Used by Rails for fragment / page caching.
|
||||
# -----------------------------------------------------------------------
|
||||
cache:
|
||||
<<: *restart_policy
|
||||
image: memcached:alpine
|
||||
networks:
|
||||
- backend
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# seeder – one-shot container
|
||||
# Runs database migrations and seeds the initial admin user.
|
||||
# Exits with code 0 after the first successful run; subsequent starts
|
||||
# are no-ops.
|
||||
# -----------------------------------------------------------------------
|
||||
seeder:
|
||||
<<: *app
|
||||
networks:
|
||||
- backend
|
||||
command: ["seeds"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_started
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# web – Puma application server (serves HTTP requests)
|
||||
# -----------------------------------------------------------------------
|
||||
web:
|
||||
<<: *app
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
command: ["web"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_started
|
||||
seeder:
|
||||
condition: service_completed_successfully
|
||||
healthcheck:
|
||||
test:
|
||||
- "CMD"
|
||||
- "curl"
|
||||
- "-f"
|
||||
- "http://localhost:8080${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}/health_checks/default"
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
expose:
|
||||
- "8080"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# worker – Active Job background processor
|
||||
# Handles asynchronous tasks such as sending notification mails,
|
||||
# exporting, repository indexing, etc.
|
||||
#
|
||||
# DNS block below resolves the SMTP issue documented in OP#44515:
|
||||
# "SMTP setup fails: Network is unreachable."
|
||||
# If your corporate DNS is sufficient, replace 8.8.8.8 with your
|
||||
# internal resolver.
|
||||
# -----------------------------------------------------------------------
|
||||
worker:
|
||||
<<: *app
|
||||
networks:
|
||||
- frontend # needs outbound access for SMTP
|
||||
- backend
|
||||
command: ["worker"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_started
|
||||
seeder:
|
||||
condition: service_completed_successfully
|
||||
# Explicit DNS – prevents "Network is unreachable" when the container
|
||||
# tries to connect to an external SMTP server. See OP#44515.
|
||||
dns:
|
||||
- "8.8.8.8"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# proxy – Caddy reverse proxy
|
||||
# Terminates TLS (if a certificate is available) and forwards to web.
|
||||
# Exposes the single public port defined by $PORT (default 8080).
|
||||
#
|
||||
# NOTE: In production, it is strongly recommended to place OpenProject
|
||||
# behind your own TLS-terminating reverse proxy (Nginx, Traefik, …)
|
||||
# and configure Caddy's trusted_proxies accordingly.
|
||||
# -----------------------------------------------------------------------
|
||||
proxy:
|
||||
<<: *restart_policy
|
||||
image: openproject/openproject:${TAG:-17-slim}
|
||||
networks:
|
||||
- frontend
|
||||
ports:
|
||||
- "${PORT:-8080}:80"
|
||||
volumes:
|
||||
- "${OPDATA:-opdata}:/var/openproject/assets"
|
||||
command: ["proxy"]
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}"
|
||||
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME:-localhost:8080}"
|
||||
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# hocuspocus – WebSocket collaboration server
|
||||
# Enables real-time co-editing of documents.
|
||||
# -----------------------------------------------------------------------
|
||||
hocuspocus:
|
||||
<<: *restart_policy
|
||||
image: openproject/openproject:${TAG:-17-slim}
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
command: ["hocuspocus"]
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET: "${COLLABORATIVE_SERVER_SECRET:-OVERRIDE_ME_PLEASE}"
|
||||
expose:
|
||||
- "3000"
|
||||
@@ -0,0 +1,149 @@
|
||||
# ==============================================================================
|
||||
# .env.example – OpenProject Docker Compose environment template
|
||||
# ==============================================================================
|
||||
# 1. cp .env.example .env
|
||||
# 2. Edit .env with real values (never commit .env to version control)
|
||||
# 3. docker compose up -d --build --pull always
|
||||
# ==============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core – OpenProject behaviour
|
||||
# ---------------------------------------------------------------------------
|
||||
# Docker image tag. Use XX-slim for the compose setup (recommended).
|
||||
TAG=17-slim
|
||||
|
||||
# Protocol flag. Set to "false" for first-run / local dev without TLS.
|
||||
# In production set to "true" and terminate TLS at your reverse proxy.
|
||||
OPENPROJECT_HTTPS=true
|
||||
|
||||
# Public hostname + optional port that appears in every generated URL and
|
||||
# e-mail link. Must match what end-users type in the browser.
|
||||
OPENPROJECT_HOST__NAME=project.martinhal.tech
|
||||
|
||||
# If OpenProject is mounted at a sub-path (e.g. /op), set it here.
|
||||
# Leave empty for root mount.
|
||||
OPENPROJECT_RAILS__RELATIVE__URL__ROOT=
|
||||
|
||||
# Edition: "standard" (community) or "bim"
|
||||
OPENPROJECT_EDITION=standard
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Database – PostgreSQL
|
||||
# ---------------------------------------------------------------------------
|
||||
# Password for the postgres superuser created by the db container.
|
||||
# Must match the :password segment in DATABASE_URL below.
|
||||
POSTGRES_PASSWORD=Chu929qw4Nf67r
|
||||
|
||||
# Full connection string. Edit only if you point at an external database.
|
||||
DATABASE_URL=postgres://postgres:Chu929qw4Nf67r@db/openproject?pool=20&encoding=unicode&reconnect=true
|
||||
|
||||
# Where Docker stores the PostgreSQL data volume.
|
||||
# Use a named volume (default) or an absolute host path.
|
||||
PGDATA=pgdata
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Attachments / assets volume
|
||||
# ---------------------------------------------------------------------------
|
||||
# Use a named volume (default) or an absolute host path, e.g.
|
||||
# OPDATA=/var/openproject/assets
|
||||
# If using an absolute path, run:
|
||||
# sudo mkdir -p /var/openproject/assets
|
||||
# sudo chown 1000:1000 -R /var/openproject/assets
|
||||
OPDATA=opdata
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Networking
|
||||
# ---------------------------------------------------------------------------
|
||||
# Port exposed by the Caddy proxy to the host.
|
||||
# Bind to 0.0.0.0 (public) or 127.0.0.1 (localhost-only).
|
||||
PORT=8080
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Threads
|
||||
# ---------------------------------------------------------------------------
|
||||
RAILS_MIN_THREADS=4
|
||||
RAILS_MAX_THREADS=16
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Collaborative editing – Hocuspocus
|
||||
# ---------------------------------------------------------------------------
|
||||
# URL where the browser connects for real-time collaboration.
|
||||
# Leave empty to use the auto-generated value: wss://<OPENPROJECT_HOST__NAME>/hocuspocus
|
||||
COLLABORATIVE_SERVER_URL=
|
||||
|
||||
# ⚠️ CHANGE THIS in production – it protects the WebSocket endpoint.
|
||||
COLLABORATIVE_SERVER_SECRET=replace_with_a_long_random_string
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Inbound email (IMAP) – optional
|
||||
# ---------------------------------------------------------------------------
|
||||
IMAP_ENABLED=false
|
||||
|
||||
# ===========================================================================
|
||||
# SMTP – Outbound e-mail ← fill these in to enable notification mails
|
||||
# ===========================================================================
|
||||
# Docs:
|
||||
# /docs/installation-and-operations/configuration/outbound-emails/
|
||||
# /docs/installation-and-operations/configuration/environment/
|
||||
#
|
||||
# These variables are forwarded to *every* OpenProject container that may
|
||||
# need to send mail (web, worker). Setting them here disables the manual
|
||||
# SMTP form inside Administration → Emails and notifications.
|
||||
# ===========================================================================
|
||||
|
||||
# Delivery method – keep as "smtp".
|
||||
EMAIL_DELIVERY_METHOD=smtp
|
||||
|
||||
# Hostname of your SMTP relay.
|
||||
# Examples:
|
||||
# Gmail → smtp.gmail.com
|
||||
# Outlook/O365 → smtp-mail.outlook.com (or smtp.office365.com)
|
||||
# SendGrid → smtp.sendgrid.net
|
||||
# Mailgun → smtp.mailgun.org
|
||||
# Custom/on-prem → smtp.yourdomain.com
|
||||
SMTP_ADDRESS=smtp.purelymail.com
|
||||
|
||||
# Port – 587 (STARTTLS, recommended) | 465 (implicit SSL) | 25 (plain, avoid)
|
||||
SMTP_PORT=587
|
||||
|
||||
# HELO / EHLO domain sent to the SMTP server.
|
||||
# Usually the public domain of your OpenProject instance.
|
||||
SMTP_DOMAIN=smtp.purelymail.com
|
||||
|
||||
# Authentication method.
|
||||
# plain – most cloud providers (Gmail, O365, SendGrid, Mailgun …)
|
||||
# login – some on-premises / legacy servers
|
||||
# cram_md5 – rarely used
|
||||
SMTP_AUTHENTICATION=plain
|
||||
|
||||
# SMTP user name.
|
||||
# Gmail → your full Gmail address
|
||||
# SendGrid → apikey (literal string)
|
||||
# Mailgun → postmaster@your-sandbox.mailgun.org
|
||||
# On-premises → usually an e-mail address
|
||||
SMTP_USER_NAME=projects@martinhakl.tech
|
||||
|
||||
# SMTP password / API key.
|
||||
# ⚠️ Never commit this file after editing – use .gitignore on .env.
|
||||
# Gmail → App Password (not your Google password)
|
||||
# SendGrid → API key starting with SG.xxxxx
|
||||
# Mailgun → API key from Mailgun dashboard
|
||||
SMTP_PASSWORD=Chu929qw4Nf67r
|
||||
|
||||
# STARTTLS – upgrade a plain connection to encrypted mid-session.
|
||||
# Set to "true" when SMTP_PORT=587 (the common case).
|
||||
# Set to "false" when SMTP_PORT=465 (implicit SSL) or 25.
|
||||
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
|
||||
# Implicit SSL – set to "true" only when SMTP_PORT=465.
|
||||
SMTP_SSL=false
|
||||
|
||||
# Connection timeout in seconds. Increase if your SMTP relay is slow.
|
||||
SMTP_TIMEOUT=5
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Envelope sender ("From" address in outgoing mails)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Must be a valid address on the SMTP account, unless your provider
|
||||
# allows arbitrary sender addresses.
|
||||
MAILER_FROM_ADDRESS=projects@martinhakl.tech
|
||||
@@ -0,0 +1,38 @@
|
||||
# ============================================================
|
||||
# UnPoller stack configuration
|
||||
# This file MUST sit in the same folder as docker-compose.yml
|
||||
# Do NOT use quotes. Do NOT put spaces around the = sign.
|
||||
# ============================================================
|
||||
|
||||
# ---------- InfluxDB (v2) ----------
|
||||
INFLUXDB_ADMIN_USER=unpoller
|
||||
INFLUXDB_ADMIN_PASSWORD=CHANGEME_influx_password
|
||||
INFLUXDB_ORG=unpoller
|
||||
INFLUXDB_BUCKET=unpoller
|
||||
# Generate a real token with: openssl rand -hex 32
|
||||
INFLUXDB_ADMIN_TOKEN=REPLACE_WITH_LONG_RANDOM_TOKEN
|
||||
|
||||
# ---------- Grafana ----------
|
||||
GRAFANA_USERNAME=admin
|
||||
GRAFANA_PASSWORD=CHANGEME_grafana_password
|
||||
|
||||
# ---------- UnPoller ----------
|
||||
POLLER_TAG=latest
|
||||
POLLER_DEBUG=false
|
||||
# Set to true only if you enabled DPI on your controller and want per-app data
|
||||
POLLER_SAVE_DPI=false
|
||||
|
||||
# ---------- UniFi Controller ----------
|
||||
# This is the account you create ON the controller (see instructions).
|
||||
UNIFI_USER=unpoller
|
||||
UNIFI_PASS=CHANGEME_set_this_on_your_controller
|
||||
#
|
||||
# IMPORTANT: This must be the address of your UniFi CONTROLLER,
|
||||
# which may or may not be the same box as your Docker host.
|
||||
#
|
||||
# * UniFi OS device (UDM / UDM-Pro / UDR / UXG / CloudKey Gen2+):
|
||||
# NO port -> https://192.168.69.180
|
||||
# * Self-hosted Network app / older CloudKey:
|
||||
# WITH port -> https://192.168.69.180:8443
|
||||
#
|
||||
UNIFI_URL=https://192.168.69.180
|
||||
@@ -0,0 +1,67 @@
|
||||
# UnPoller + InfluxDB 2.x + Grafana
|
||||
# Run on the Docker host at 192.168.69.180
|
||||
# All secrets/config live in the .env file next to this file.
|
||||
|
||||
services:
|
||||
influxdb:
|
||||
image: influxdb:2.7
|
||||
container_name: unpoller-influxdb
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '8086:8086'
|
||||
volumes:
|
||||
- influxdb-storage:/var/lib/influxdb2
|
||||
- influxdb-config:/etc/influxdb2
|
||||
environment:
|
||||
- DOCKER_INFLUXDB_INIT_MODE=setup
|
||||
- DOCKER_INFLUXDB_INIT_USERNAME=${INFLUXDB_ADMIN_USER}
|
||||
- DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_ADMIN_PASSWORD}
|
||||
- DOCKER_INFLUXDB_INIT_ORG=${INFLUXDB_ORG}
|
||||
- DOCKER_INFLUXDB_INIT_BUCKET=${INFLUXDB_BUCKET}
|
||||
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_ADMIN_TOKEN}
|
||||
- DOCKER_INFLUXDB_INIT_RETENTION=0s # 0s = keep data forever
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: unpoller-grafana
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '3000:3000'
|
||||
volumes:
|
||||
- grafana-storage:/var/lib/grafana
|
||||
depends_on:
|
||||
- influxdb
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_USER=${GRAFANA_USERNAME}
|
||||
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
|
||||
- GF_INSTALL_PLUGINS=grafana-clock-panel,natel-discrete-panel,grafana-piechart-panel
|
||||
|
||||
unpoller:
|
||||
image: ghcr.io/unpoller/unpoller:${POLLER_TAG}
|
||||
container_name: unpoller
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '9130:9130' # Prometheus /metrics endpoint (optional health check)
|
||||
depends_on:
|
||||
- influxdb
|
||||
- grafana
|
||||
environment:
|
||||
# ---- InfluxDB 2.x output ----
|
||||
- UP_INFLUXDB_URL=http://influxdb:8086
|
||||
- UP_INFLUXDB_ORG=${INFLUXDB_ORG}
|
||||
- UP_INFLUXDB_BUCKET=${INFLUXDB_BUCKET}
|
||||
- UP_INFLUXDB_AUTH_TOKEN=${INFLUXDB_ADMIN_TOKEN}
|
||||
# ---- UniFi controller to poll ----
|
||||
- UP_UNIFI_DEFAULT_URL=${UNIFI_URL}
|
||||
- UP_UNIFI_DEFAULT_USER=${UNIFI_USER}
|
||||
- UP_UNIFI_DEFAULT_PASS=${UNIFI_PASS}
|
||||
- UP_UNIFI_DEFAULT_VERIFY_SSL=false # UniFi uses a self-signed cert
|
||||
- UP_UNIFI_DEFAULT_SAVE_SITES=true
|
||||
- UP_UNIFI_DEFAULT_SAVE_DPI=${POLLER_SAVE_DPI}
|
||||
# ---- General ----
|
||||
- UP_POLLER_DEBUG=${POLLER_DEBUG}
|
||||
|
||||
volumes:
|
||||
influxdb-storage:
|
||||
influxdb-config:
|
||||
grafana-storage:
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
uptime-kuma:
|
||||
image: louislam/uptime-kuma:2
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
ports:
|
||||
# <Host Port>:<Container Port>
|
||||
- "3001:3001"
|
||||
@@ -0,0 +1,75 @@
|
||||
# =============================================================================
|
||||
# Vikunja – environment variables
|
||||
# Copy this file to .env and fill in real values. The .env file is read
|
||||
# automatically by "docker compose" in the same directory.
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 1. Core / Service
|
||||
# -----------------------------------------------------------------------------
|
||||
# The URL browsers will use to reach Vikunja (no trailing slash).
|
||||
# This is mandatory when CORS is enabled (the default).
|
||||
PUBLIC_URL=http://localhost:3456
|
||||
|
||||
# A long, random secret used to sign JWT tokens.
|
||||
# Generate one with: openssl rand -hex 32
|
||||
JWT_SECRET=REPLACE_ME_WITH_A_RANDOM_64_CHAR_HEX_STRING
|
||||
|
||||
# IANA tz name – e.g. Europe/Lisbon, America/New_York, Asia/Tokyo
|
||||
TIMEZONE=Europe/Lisbon
|
||||
|
||||
# Set to "false" after you have created all desired user accounts.
|
||||
ENABLE_REGISTRATION=true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2. PostgreSQL
|
||||
# -----------------------------------------------------------------------------
|
||||
DB_USER=vikunja
|
||||
DB_PASSWORD=REPLACE_WITH_A_STRONG_PASSWORD
|
||||
DB_NAME=vikunja
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 3. SMTP / Mailer
|
||||
# -----------------------------------------------------------------------------
|
||||
# --- Host & port ------------------------------------------------------------
|
||||
# Common presets:
|
||||
# Gmail smtp.gmail.com 587 (STARTTLS) or 465 (SSL)
|
||||
# Outlook/M365 smtp-auth.outlook.com 587
|
||||
# Postfix (local) 127.0.0.1 25 (set SMTP_FORCE_SSL=false, AUTH=none)
|
||||
# Mailgun smtp.mailgun.org 587
|
||||
# Custom/self your.smtp.server 587
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
|
||||
# --- Credentials ------------------------------------------------------------
|
||||
# For Gmail with 2-FA enabled, use an *App Password*, not your real password.
|
||||
SMTP_USER=your-email@gmail.com
|
||||
SMTP_PASSWORD=REPLACE_WITH_APP_OR_REAL_PASSWORD
|
||||
|
||||
# --- Sender address ---------------------------------------------------------
|
||||
# The "From:" field in outgoing mails. Many providers require this to match
|
||||
# SMTP_USER or an authorised alias.
|
||||
SMTP_FROM=your-email@gmail.com
|
||||
|
||||
# --- TLS / SSL options ------------------------------------------------------
|
||||
# SMTP_FORCE_SSL – wraps the connection in TLS immediately on connect.
|
||||
# • true → typically paired with port 465 (implicit TLS / SMTPS)
|
||||
# • false → expects STARTTLS on port 587 (or plain on 25)
|
||||
SMTP_FORCE_SSL=false
|
||||
|
||||
# SMTP_SKIP_TLS_VERIFY – skip server-certificate validation.
|
||||
# Set to true ONLY if you use a self-signed certificate.
|
||||
SMTP_SKIP_TLS_VERIFY=false
|
||||
|
||||
# SMTP_AUTH_TYPE – authentication method the SMTP server expects:
|
||||
# plain – username + password in a single base64 blob (default)
|
||||
# login – username and password sent in two separate challenges
|
||||
# none – no authentication (e.g. local Postfix accepting anonymous relay)
|
||||
SMTP_AUTH_TYPE=plain
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 4. Optional – host-side port mapping
|
||||
# -----------------------------------------------------------------------------
|
||||
# The port exposed on the Docker host (maps to container port 3456).
|
||||
# Leave empty to default to 3456.
|
||||
HOST_PORT=3456
|
||||
@@ -0,0 +1,98 @@
|
||||
# =============================================================================
|
||||
# Vikunja — Docker Compose with PostgreSQL + SMTP e-mail support
|
||||
# =============================================================================
|
||||
# Before starting the stack run:
|
||||
#
|
||||
# mkdir -p ./files ./db
|
||||
# chown 1000:1000 ./files # Vikunja runs as UID 1000
|
||||
# chown 999:999 ./db # PostgreSQL runs as UID 999
|
||||
#
|
||||
# (On some systems you may need: sudo chown -R 999:999 ./db)
|
||||
#
|
||||
# Then copy .env.example → .env and fill in every value.
|
||||
# Start with: docker compose up -d
|
||||
# Test mailer: docker compose exec vikunja /app/vikunja/vikunja testmail <address>
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# PostgreSQL database
|
||||
# ---------------------------------------------------------------------------
|
||||
db:
|
||||
image: postgres:18
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
user: "999:999" # PostgreSQL official image default UID/GID
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
volumes:
|
||||
# PostgreSQL 18+ requires mounting at /var/lib/postgresql (not /data subfolder)
|
||||
# Data will be stored in ./db/18/data automatically
|
||||
- ./db:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vikunja (API + frontend in one container)
|
||||
# ---------------------------------------------------------------------------
|
||||
vikunja:
|
||||
image: vikunja/vikunja:latest
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
# The container defaults to UID 1000 / no group.
|
||||
# Adjust if your host folders need a different owner.
|
||||
# user: "1000:1000"
|
||||
ports:
|
||||
- "${HOST_PORT:-3456}:3456"
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files # uploaded / generated files
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy # wait for postgres to be ready
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Environment — all sensitive values come from .env
|
||||
# -----------------------------------------------------------------------
|
||||
environment:
|
||||
# --- Core service -------------------------------------------------------
|
||||
VIKUNJA_SERVICE_PUBLICURL: ${PUBLIC_URL}
|
||||
VIKUNJA_SERVICE_JWTSECRET: ${JWT_SECRET}
|
||||
VIKUNJA_SERVICE_TIMEZONE: ${TIMEZONE:-UTC}
|
||||
VIKUNJA_SERVICE_ENABLEREGISTRATION: ${ENABLE_REGISTRATION:-true}
|
||||
VIKUNJA_SERVICE_ENABLETASKATTACHMENTS: "true"
|
||||
VIKUNJA_SERVICE_ENABLEEMAILREMINDERS: "true"
|
||||
|
||||
# --- Database ------------------------------------------------------------
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_USER: ${DB_USER}
|
||||
VIKUNJA_DATABASE_PASSWORD: ${DB_PASSWORD}
|
||||
VIKUNJA_DATABASE_DATABASE: ${DB_NAME}
|
||||
VIKUNJA_DATABASE_SSLMODE: disable # set to 'require' if TLS is enabled on PG
|
||||
|
||||
# --- SMTP / Mailer -------------------------------------------------------
|
||||
# When VIKUNJA_MAILER_ENABLED is true Vikunja sends:
|
||||
# • registration confirmation mails
|
||||
# • password-reset mails
|
||||
# • task-reminder mails (when ENABLEEMAILREMINDERS is also true)
|
||||
VIKUNJA_MAILER_ENABLED: "true"
|
||||
VIKUNJA_MAILER_HOST: ${SMTP_HOST}
|
||||
VIKUNJA_MAILER_PORT: ${SMTP_PORT:-587}
|
||||
VIKUNJA_MAILER_USERNAME: ${SMTP_USER}
|
||||
VIKUNJA_MAILER_PASSWORD: ${SMTP_PASSWORD}
|
||||
VIKUNJA_MAILER_FROMEMAIL: ${SMTP_FROM}
|
||||
VIKUNJA_MAILER_FORCESSL: ${SMTP_FORCE_SSL:-true} # wraps the connection in TLS from the start (use with port 465)
|
||||
VIKUNJA_MAILER_SKIPTLSVERIFY: ${SMTP_SKIP_TLS_VERIFY:-false} # set true only for self-signed certs
|
||||
VIKUNJA_MAILER_AUTHTYPE: ${SMTP_AUTH_TYPE:-plain} # plain | login | none
|
||||
|
||||
# --- CORS (required when frontend & API share the same origin) ----------
|
||||
VIKUNJA_CORS_ENABLE: "true"
|
||||
VIKUNJA_CORS_ORIGINS: ${PUBLIC_URL}
|
||||
@@ -0,0 +1,75 @@
|
||||
# =============================================================================
|
||||
# Vikunja – environment variables
|
||||
# Copy this file to .env and fill in real values. The .env file is read
|
||||
# automatically by "docker compose" in the same directory.
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 1. Core / Service
|
||||
# -----------------------------------------------------------------------------
|
||||
# The URL browsers will use to reach Vikunja (no trailing slash).
|
||||
# This is mandatory when CORS is enabled (the default).
|
||||
PUBLIC_URL=http://localhost:3456
|
||||
|
||||
# A long, random secret used to sign JWT tokens.
|
||||
# Generate one with: openssl rand -hex 32
|
||||
JWT_SECRET=REPLACE_ME_WITH_A_RANDOM_64_CHAR_HEX_STRING
|
||||
|
||||
# IANA tz name – e.g. Europe/Lisbon, America/New_York, Asia/Tokyo
|
||||
TIMEZONE=Europe/Lisbon
|
||||
|
||||
# Set to "false" after you have created all desired user accounts.
|
||||
ENABLE_REGISTRATION=true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2. PostgreSQL
|
||||
# -----------------------------------------------------------------------------
|
||||
DB_USER=vikunja
|
||||
DB_PASSWORD=REPLACE_WITH_A_STRONG_PASSWORD
|
||||
DB_NAME=vikunja
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 3. SMTP / Mailer
|
||||
# -----------------------------------------------------------------------------
|
||||
# --- Host & port ------------------------------------------------------------
|
||||
# Common presets:
|
||||
# Gmail smtp.gmail.com 587 (STARTTLS) or 465 (SSL)
|
||||
# Outlook/M365 smtp-auth.outlook.com 587
|
||||
# Postfix (local) 127.0.0.1 25 (set SMTP_FORCE_SSL=false, AUTH=none)
|
||||
# Mailgun smtp.mailgun.org 587
|
||||
# Custom/self your.smtp.server 587
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
|
||||
# --- Credentials ------------------------------------------------------------
|
||||
# For Gmail with 2-FA enabled, use an *App Password*, not your real password.
|
||||
SMTP_USER=your-email@gmail.com
|
||||
SMTP_PASSWORD=REPLACE_WITH_APP_OR_REAL_PASSWORD
|
||||
|
||||
# --- Sender address ---------------------------------------------------------
|
||||
# The "From:" field in outgoing mails. Many providers require this to match
|
||||
# SMTP_USER or an authorised alias.
|
||||
SMTP_FROM=your-email@gmail.com
|
||||
|
||||
# --- TLS / SSL options ------------------------------------------------------
|
||||
# SMTP_FORCE_SSL – wraps the connection in TLS immediately on connect.
|
||||
# • true → typically paired with port 465 (implicit TLS / SMTPS)
|
||||
# • false → expects STARTTLS on port 587 (or plain on 25)
|
||||
SMTP_FORCE_SSL=false
|
||||
|
||||
# SMTP_SKIP_TLS_VERIFY – skip server-certificate validation.
|
||||
# Set to true ONLY if you use a self-signed certificate.
|
||||
SMTP_SKIP_TLS_VERIFY=false
|
||||
|
||||
# SMTP_AUTH_TYPE – authentication method the SMTP server expects:
|
||||
# plain – username + password in a single base64 blob (default)
|
||||
# login – username and password sent in two separate challenges
|
||||
# none – no authentication (e.g. local Postfix accepting anonymous relay)
|
||||
SMTP_AUTH_TYPE=plain
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 4. Optional – host-side port mapping
|
||||
# -----------------------------------------------------------------------------
|
||||
# The port exposed on the Docker host (maps to container port 3456).
|
||||
# Leave empty to default to 3456.
|
||||
HOST_PORT=3456
|
||||
Binary file not shown.
Reference in New Issue
Block a user