This commit is contained in:
jpmvaz
2026-09-13 20:27:51 +01:00
commit 37b00787b9
23 changed files with 2581 additions and 0 deletions
+443
View File
@@ -0,0 +1,443 @@
<div align="center">
# 📚 Documize Community — Docker Deployment
**Self-hosted knowledge management · v5.14.0 · MySQL 8 · Docker Compose**
[![Documize](https://img.shields.io/badge/Documize-v5.14.0-2563EB?style=for-the-badge&logo=gitbook&logoColor=white)](https://github.com/documize/community)
[![MySQL](https://img.shields.io/badge/MySQL-8.0-4479A1?style=for-the-badge&logo=mysql&logoColor=white)](https://hub.docker.com/_/mysql)
[![Docker](https://img.shields.io/badge/Docker-Compose-2496ED?style=for-the-badge&logo=docker&logoColor=white)](https://docs.docker.com/compose/)
[![License](https://img.shields.io/badge/License-GNU_AGPL_v3-22C55E?style=for-the-badge)](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 (~3060 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>
+134
View File
@@ -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
+59
View File
@@ -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
View File
@@ -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=
+95
View File
@@ -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)
+51
View File
@@ -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
+15
View File
@@ -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'
+15
View File
@@ -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'
+374
View File
@@ -0,0 +1,374 @@
# 🎬 Jellyfin — Self-Hosted Media Server
![Jellyfin](https://img.shields.io/badge/Jellyfin-00A4DC?style=for-the-badge&logo=jellyfin&logoColor=white)
![Docker](https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white)
![License](https://img.shields.io/badge/License-GPL--2.0-blue?style=for-the-badge)
---
## 📖 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*
+79
View File
@@ -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"
+51
View File
@@ -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
+144
View File
@@ -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.
+302
View File
@@ -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"
+280
View File
@@ -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"
+149
View File
@@ -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
+38
View File
@@ -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:
+9
View File
@@ -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"
+75
View File
@@ -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
+98
View File
@@ -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}
+75
View File
@@ -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
BIN
View File
Binary file not shown.