chore: Update deployment configuration and documentation

- Update Gitea configuration (remove DEFAULT_ACTIONS_URL)
- Fix deployment documentation
- Update Ansible playbooks
- Clean up deprecated files
- Add new deployment scripts and templates
This commit is contained in:
2025-10-31 21:11:11 +01:00
parent cf4748f8db
commit 16d586ecdf
92 changed files with 4601 additions and 10524 deletions

View File

@@ -38,3 +38,11 @@ QUEUE_CONNECTION=default
QUEUE_WORKER_SLEEP=3
QUEUE_WORKER_TRIES=3
QUEUE_WORKER_TIMEOUT=60
# Git Repository Configuration (optional - if set, container will clone/pull code on start)
# Uncomment to enable Git-based deployment:
# GIT_REPOSITORY_URL=https://git.michaelschiemer.de/michael/michaelschiemer.git
# GIT_BRANCH=main
# GIT_TOKEN=
# GIT_USERNAME=
# GIT_PASSWORD=

View File

@@ -1,10 +1,9 @@
version: '3.8'
# Docker Registry: registry.michaelschiemer.de (HTTPS via Traefik)
services:
# PHP-FPM Application Runtime
app:
image: registry.michaelschiemer.de/framework:latest
image: git.michaelschiemer.de:5000/framework:latest
container_name: app
restart: unless-stopped
networks:
@@ -55,8 +54,9 @@ services:
condition: service_started
# Nginx Web Server
# Uses same image as app - clones code from Git if GIT_REPOSITORY_URL is set, then runs nginx
nginx:
image: nginx:1.25-alpine
image: git.michaelschiemer.de:5000/framework:latest
container_name: nginx
restart: unless-stopped
networks:
@@ -64,12 +64,89 @@ services:
- app-internal
environment:
- TZ=Europe/Berlin
- APP_ENV=${APP_ENV:-production}
- APP_DEBUG=${APP_DEBUG:-false}
# Git Repository (same as app - will clone code on start)
- GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL:-}
- GIT_BRANCH=${GIT_BRANCH:-main}
- GIT_TOKEN=${GIT_TOKEN:-}
- GIT_USERNAME=${GIT_USERNAME:-}
- GIT_PASSWORD=${GIT_PASSWORD:-}
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- app-code:/var/www/html:ro
- app-storage:/var/www/html/storage:ro
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
# Use custom entrypoint that ensures code is available then starts nginx only (no PHP-FPM)
entrypoint: ["/bin/sh", "-c"]
command:
- |
# Ensure code is available in /var/www/html (from image or Git)
GIT_TARGET_DIR="/var/www/html"
# If storage is mounted but code is missing, copy from image's original location
if [ ! -d "$$GIT_TARGET_DIR/public" ] && [ -d "/var/www/html.orig" ]; then
echo "?? [nginx] Copying code from image..."
# Copy everything except storage (which is a volume mount)
find /var/www/html.orig -mindepth 1 -maxdepth 1 ! -name "storage" -exec cp -r {} "$$GIT_TARGET_DIR/" \; 2>/dev/null || true
fi
if [ -n "$$GIT_REPOSITORY_URL" ]; then
# Configure Git to be non-interactive
export GIT_TERMINAL_PROMPT=0
export GIT_ASKPASS=echo
# Determine authentication method
if [ -n "$$GIT_TOKEN" ]; then
GIT_URL_WITH_AUTH=$$(echo "$$GIT_REPOSITORY_URL" | sed "s|https://|https://$${GIT_TOKEN}@|")
elif [ -n "$$GIT_USERNAME" ] && [ -n "$$GIT_PASSWORD" ]; then
GIT_URL_WITH_AUTH=$$(echo "$$GIT_REPOSITORY_URL" | sed "s|https://|https://$${GIT_USERNAME}:$${GIT_PASSWORD}@|")
else
echo "⚠️ [nginx] No Git credentials provided (GIT_TOKEN or GIT_USERNAME/GIT_PASSWORD). Using image contents."
GIT_URL_WITH_AUTH=""
fi
if [ -n "$$GIT_URL_WITH_AUTH" ] && [ ! -d "$$GIT_TARGET_DIR/.git" ]; then
echo "?? [nginx] Cloning repository from $$GIT_REPOSITORY_URL (branch: $${GIT_BRANCH:-main})..."
# Remove only files/dirs that are not storage (which is a volume mount)
# Clone into a temporary directory first, then move contents
TEMP_CLONE="$${GIT_TARGET_DIR}.tmp"
rm -rf "$$TEMP_CLONE" 2>/dev/null || true
if git clone --branch "$${GIT_BRANCH:-main}" --depth 1 "$$GIT_URL_WITH_AUTH" "$$TEMP_CLONE"; then
# Remove only files/dirs that are not storage (which is a volume mount)
find "$$GIT_TARGET_DIR" -mindepth 1 -maxdepth 1 ! -name "storage" -exec rm -rf {} \\; 2>/dev/null || true
# Move contents from temp directory to target (preserving storage)
find "$$TEMP_CLONE" -mindepth 1 -maxdepth 1 ! -name "." ! -name ".." -exec mv {} "$$GIT_TARGET_DIR/" \\; 2>/dev/null || true
rm -rf "$$TEMP_CLONE" 2>/dev/null || true
echo "✅ [nginx] Repository cloned successfully"
else
echo "? Git clone failed. Using image contents."
rm -rf "$$TEMP_CLONE" 2>/dev/null || true
fi
else
echo "?? [nginx] Pulling latest changes..."
cd "$$GIT_TARGET_DIR"
git fetch origin "$${GIT_BRANCH:-main}" || true
git reset --hard "origin/$${GIT_BRANCH:-main}" || true
git clean -fd || true
fi
if [ -f "$$GIT_TARGET_DIR/composer.json" ]; then
echo "?? [nginx] Installing dependencies..."
cd "$$GIT_TARGET_DIR"
composer install --no-dev --optimize-autoloader --no-interaction --no-scripts || true
composer dump-autoload --optimize --classmap-authoritative || true
fi
echo "? [nginx] Git sync completed"
else
echo "?? [nginx] GIT_REPOSITORY_URL not set, using code from image"
fi
# Start nginx only (no PHP-FPM)
echo "?? [nginx] Starting nginx..."
exec nginx -g "daemon off;"
labels:
- "traefik.enable=true"
# HTTP Router
@@ -84,7 +161,7 @@ services:
# Network
- "traefik.docker.network=traefik-public"
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1/health || exit 1"]
test: ["CMD-SHELL", "curl -f http://127.0.0.1/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
@@ -125,7 +202,7 @@ services:
# Queue Worker (Background Jobs)
queue-worker:
image: registry.michaelschiemer.de/framework:latest
image: git.michaelschiemer.de:5000/framework:latest
container_name: queue-worker
restart: unless-stopped
networks:
@@ -170,7 +247,7 @@ services:
# Scheduler (Cron Jobs)
scheduler:
image: registry.michaelschiemer.de/framework:latest
image: git.michaelschiemer.de:5000/framework:latest
container_name: scheduler
restart: unless-stopped
networks:

View File

@@ -0,0 +1,286 @@
version: '3.8'
# Docker Registry: registry.michaelschiemer.de (HTTPS via Traefik)
services:
# PHP-FPM Application Runtime
app:
image: git.michaelschiemer.de:5000/framework:latest
container_name: app
restart: unless-stopped
networks:
- app-internal
environment:
- TZ=Europe/Berlin
- APP_ENV=${APP_ENV:-production}
- APP_DEBUG=${APP_DEBUG:-false}
- APP_URL=${APP_URL:-https://michaelschiemer.de}
# Git Repository (optional - if set, container will clone/pull code on start)
- GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL:-}
- GIT_BRANCH=${GIT_BRANCH:-main}
- GIT_TOKEN=${GIT_TOKEN:-}
- GIT_USERNAME=${GIT_USERNAME:-}
- GIT_PASSWORD=${GIT_PASSWORD:-}
# Database
- DB_HOST=${DB_HOST:-postgres}
- DB_PORT=${DB_PORT:-5432}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
# Redis
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
# Cache
- CACHE_DRIVER=redis
- CACHE_PREFIX=${CACHE_PREFIX:-app}
# Session
- SESSION_DRIVER=redis
- SESSION_LIFETIME=${SESSION_LIFETIME:-120}
# Queue
- QUEUE_DRIVER=redis
- QUEUE_CONNECTION=default
volumes:
- app-storage:/var/www/html/storage
- app-logs:/var/www/html/storage/logs
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
healthcheck:
test: ["CMD-SHELL", "true"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
depends_on:
redis:
condition: service_started
# Nginx Web Server
# Uses same image as app - clones code from Git if GIT_REPOSITORY_URL is set, then runs nginx
nginx:
image: git.michaelschiemer.de:5000/framework:latest
container_name: nginx
restart: unless-stopped
networks:
- traefik-public
- app-internal
environment:
- TZ=Europe/Berlin
- APP_ENV=${APP_ENV:-production}
- APP_DEBUG=${APP_DEBUG:-false}
# Git Repository (same as app - will clone code on start)
- GIT_REPOSITORY_URL=${GIT_REPOSITORY_URL:-}
- GIT_BRANCH=${GIT_BRANCH:-main}
- GIT_TOKEN=${GIT_TOKEN:-}
- GIT_USERNAME=${GIT_USERNAME:-}
- GIT_PASSWORD=${GIT_PASSWORD:-}
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- app-storage:/var/www/html/storage:ro
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
# Use custom entrypoint that ensures code is available then starts nginx only (no PHP-FPM)
entrypoint: ["/bin/sh", "-c"]
command:
- |
# Ensure code is available in /var/www/html (from image or Git)
GIT_TARGET_DIR="/var/www/html"
# If storage is mounted but code is missing, copy from image's original location
if [ ! -d "$$GIT_TARGET_DIR/public" ] && [ -d "/var/www/html.orig" ]; then
echo "?? [nginx] Copying code from image..."
# Copy everything except storage (which is a volume mount)
find /var/www/html.orig -mindepth 1 -maxdepth 1 ! -name "storage" -exec cp -r {} "$$GIT_TARGET_DIR/" \; 2>/dev/null || true
fi
if [ -n "$$GIT_REPOSITORY_URL" ]; then
# Determine authentication method
if [ -n "$$GIT_TOKEN" ]; then
GIT_URL_WITH_AUTH=$$(echo "$$GIT_REPOSITORY_URL" | sed "s|https://|https://$${GIT_TOKEN}@|")
elif [ -n "$$GIT_USERNAME" ] && [ -n "$$GIT_PASSWORD" ]; then
GIT_URL_WITH_AUTH=$$(echo "$$GIT_REPOSITORY_URL" | sed "s|https://|https://$${GIT_USERNAME}:$${GIT_PASSWORD}@|")
else
GIT_URL_WITH_AUTH="$$GIT_REPOSITORY_URL"
fi
if [ ! -d "$$GIT_TARGET_DIR/.git" ]; then
echo "?? [nginx] Cloning repository from $$GIT_REPOSITORY_URL (branch: $${GIT_BRANCH:-main})..."
# Remove only files/dirs that are not storage (which is a volume mount)
find "$$GIT_TARGET_DIR" -mindepth 1 -maxdepth 1 ! -name "storage" -exec rm -rf {} \; 2>/dev/null || true
git clone --branch "$${GIT_BRANCH:-main}" --depth 1 "$$GIT_URL_WITH_AUTH" "$$GIT_TARGET_DIR" || {
echo "? Git clone failed. Using image contents."
}
else
echo "?? [nginx] Pulling latest changes..."
cd "$$GIT_TARGET_DIR"
git fetch origin "$${GIT_BRANCH:-main}" || true
git reset --hard "origin/$${GIT_BRANCH:-main}" || true
git clean -fd || true
fi
if [ -f "$$GIT_TARGET_DIR/composer.json" ]; then
echo "?? [nginx] Installing dependencies..."
cd "$$GIT_TARGET_DIR"
composer install --no-dev --optimize-autoloader --no-interaction --no-scripts || true
composer dump-autoload --optimize --classmap-authoritative || true
fi
echo "? [nginx] Git sync completed"
else
echo "?? [nginx] GIT_REPOSITORY_URL not set, using code from image"
fi
# Start nginx only (no PHP-FPM)
echo "?? [nginx] Starting nginx..."
exec nginx -g "daemon off;"
labels:
- "traefik.enable=true"
# HTTP Router
- "traefik.http.routers.app.rule=Host(`${APP_DOMAIN:-michaelschiemer.de}`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls=true"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
# Service
- "traefik.http.services.app.loadbalancer.server.port=80"
# Middleware
- "traefik.http.routers.app.middlewares=default-chain@file"
# Network
- "traefik.docker.network=traefik-public"
healthcheck:
test: ["CMD-SHELL", "curl -f http://127.0.0.1/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
depends_on:
app:
condition: service_started
# Redis Cache/Session/Queue Backend
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
networks:
- app-internal
environment:
- TZ=Europe/Berlin
command: >
redis-server
--requirepass ${REDIS_PASSWORD}
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--save 900 1
--save 300 10
--save 60 10000
--appendonly yes
--appendfsync everysec
volumes:
- redis-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# Queue Worker (Background Jobs)
queue-worker:
image: git.michaelschiemer.de:5000/framework:latest
container_name: queue-worker
restart: unless-stopped
networks:
- app-internal
environment:
- TZ=Europe/Berlin
- APP_ENV=${APP_ENV:-production}
- APP_DEBUG=${APP_DEBUG:-false}
# Database
- DB_HOST=${DB_HOST:-postgres}
- DB_PORT=${DB_PORT:-5432}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
# Redis
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
# Queue
- QUEUE_DRIVER=redis
- QUEUE_CONNECTION=default
- QUEUE_WORKER_SLEEP=${QUEUE_WORKER_SLEEP:-3}
- QUEUE_WORKER_TRIES=${QUEUE_WORKER_TRIES:-3}
- QUEUE_WORKER_TIMEOUT=${QUEUE_WORKER_TIMEOUT:-60}
volumes:
- app-storage:/var/www/html/storage
- app-logs:/var/www/html/storage/logs
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
command: php console.php queue:work --queue=default --timeout=${QUEUE_WORKER_TIMEOUT:-60}
healthcheck:
test: ["CMD-SHELL", "php -r 'exit(0);' && test -f /var/www/html/console.php || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
depends_on:
app:
condition: service_started
redis:
condition: service_started
# Scheduler (Cron Jobs)
scheduler:
image: git.michaelschiemer.de:5000/framework:latest
container_name: scheduler
restart: unless-stopped
networks:
- app-internal
environment:
- TZ=Europe/Berlin
- APP_ENV=${APP_ENV:-production}
- APP_DEBUG=${APP_DEBUG:-false}
# Database
- DB_HOST=${DB_HOST:-postgres}
- DB_PORT=${DB_PORT:-5432}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
# Redis
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
volumes:
- app-storage:/var/www/html/storage
- app-logs:/var/www/html/storage/logs
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
command: php console.php scheduler:run
healthcheck:
test: ["CMD-SHELL", "php -r 'exit(0);' && test -f /var/www/html/console.php || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
depends_on:
app:
condition: service_started
redis:
condition: service_started
volumes:
app-code:
name: app-code
app-storage:
name: app-storage
app-logs:
name: app-logs
redis-data:
name: redis-data
networks:
traefik-public:
external: true
app-internal:
external: true
name: app-internal

View File

@@ -119,6 +119,21 @@ docker compose ps
git clone ssh://git@git.michaelschiemer.de:2222/username/repo.git
```
### Configuration File
Gitea configuration is managed via `app.ini` file:
- **Local file**: `deployment/stacks/gitea/app.ini` (for local development)
- **Production**: Generated from Ansible template `deployment/ansible/templates/gitea-app.ini.j2`
- The `app.ini` is mounted read-only into the container at `/data/gitea/conf/app.ini`
- Configuration is based on the official Gitea example: https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini
**Key Configuration Sections:**
- `[server]`: Domain, ports, SSH settings
- `[database]`: PostgreSQL connection
- `[actions]`: Actions enabled, no GitHub dependency
- `[service]`: Registration settings
- `[cache]` / `[session]` / `[queue]`: Storage configuration
### Gitea Actions
Gitea Actions (GitHub Actions compatible) are enabled by default. To use them:

View File

@@ -0,0 +1,80 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Gitea Configuration File
;; This file is based on the official Gitea example configuration
;; https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General Settings
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
APP_NAME = Gitea: Git with a cup of tea
RUN_MODE = prod
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Server Configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[server]
PROTOCOL = http
DOMAIN = git.michaelschiemer.de
HTTP_ADDR = 0.0.0.0
HTTP_PORT = 3000
ROOT_URL = https://git.michaelschiemer.de/
PUBLIC_URL_DETECTION = auto
;; SSH Configuration
DISABLE_SSH = false
START_SSH_SERVER = true
SSH_DOMAIN = git.michaelschiemer.de
SSH_PORT = 22
SSH_LISTEN_PORT = 22
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Database Configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[database]
DB_TYPE = postgres
HOST = postgres:5432
NAME = gitea
USER = gitea
PASSWD = gitea_password
SSL_MODE = disable
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Cache Configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[cache]
ENABLED = false
ADAPTER = memory
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Session Configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[session]
PROVIDER = file
PROVIDER_CONFIG = data/sessions
COOKIE_SECURE = true
COOKIE_NAME = i_like_gitea
GC_INTERVAL_TIME = 86400
SESSION_LIFE_TIME = 86400
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Queue Configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[queue]
TYPE = channel
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Service Configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[service]
DISABLE_REGISTRATION = true
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Actions Configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[actions]
ENABLED = true
;; Use "self" to use the current Gitea instance for actions (not GitHub)
;; Do NOT set DEFAULT_ACTIONS_URL to a custom URL - it's not supported
;; Leaving it unset or setting to "self" will use the current instance
;DEFAULT_ACTIONS_URL = self

View File

@@ -1,5 +1,3 @@
version: '3.8'
services:
gitea:
image: gitea/gitea:1.25

View File

@@ -0,0 +1,17 @@
# MinIO Object Storage Stack Configuration
# Copy this file to .env and adjust values
# Timezone
TZ=Europe/Berlin
# MinIO Root Credentials
# Generate secure password with: openssl rand -base64 32
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=<generate-with-openssl-rand-base64-32>
# Domain Configuration
# API endpoint (S3-compatible)
MINIO_API_DOMAIN=minio-api.michaelschiemer.de
# Console endpoint (Web UI)
MINIO_CONSOLE_DOMAIN=minio.michaelschiemer.de

View File

@@ -0,0 +1,657 @@
# MinIO Object Storage Stack - S3-compatible Object Storage
## Overview
MinIO ist ein hochperformanter, S3-kompatibler Object Storage Service für private Cloud- und Edge-Computing-Umgebungen.
**Features**:
- S3-kompatible API (Port 9000)
- Web-basierte Console für Management (Port 9001)
- SSL via Traefik
- Persistent storage
- Health checks und Monitoring
- Multi-Tenant Bucket Management
## Services
- **minio-api.michaelschiemer.de** - S3-kompatible API Endpoint
- **minio.michaelschiemer.de** - Web Console (Management UI)
## Prerequisites
1. **Traefik Stack Running**
```bash
cd ../traefik
docker compose up -d
```
2. **DNS Configuration**
Point these domains to your server IP (94.16.110.151):
- `minio-api.michaelschiemer.de`
- `minio.michaelschiemer.de`
## Configuration
### 1. Create Environment File
```bash
cp .env.example .env
```
### 2. Generate MinIO Root Password
```bash
openssl rand -base64 32
```
Update `.env`:
```env
MINIO_ROOT_PASSWORD=<generated-password>
```
**Important**: Change default `MINIO_ROOT_USER` in production!
### 3. Adjust Domains (Optional)
Edit `.env` to customize domains:
```env
MINIO_API_DOMAIN=storage-api.example.com
MINIO_CONSOLE_DOMAIN=storage.example.com
```
## Deployment
### Initial Setup
```bash
# Ensure Traefik is running
docker network inspect traefik-public
# Start MinIO
docker compose up -d
# Check logs
docker compose logs -f
# Verify health
docker compose ps
```
### Verify Deployment
```bash
# Test API endpoint
curl -I https://minio-api.michaelschiemer.de/minio/health/live
# Expected: HTTP/2 200
# Access Console
open https://minio.michaelschiemer.de
```
## Usage
### Web Console Access
1. Navigate to: https://minio.michaelschiemer.de
2. Login with:
- **Access Key**: Value of `MINIO_ROOT_USER`
- **Secret Key**: Value of `MINIO_ROOT_PASSWORD`
### Create Bucket via Console
1. Login to Console
2. Click "Create Bucket"
3. Enter bucket name (e.g., `my-bucket`)
4. Configure bucket settings:
- Versioning
- Object Locking
- Quota
- Retention policies
### S3 API Access
#### Using AWS CLI
```bash
# Install AWS CLI (if not installed)
pip install awscli
# Configure MinIO endpoint
aws configure set default.s3.signature_version s3v4
aws configure set default.s3.addressing_style virtual
# Set credentials
export AWS_ACCESS_KEY_ID=minioadmin
export AWS_SECRET_ACCESS_KEY=<MINIO_ROOT_PASSWORD>
# Test connection
aws --endpoint-url https://minio-api.michaelschiemer.de s3 ls
# Create bucket
aws --endpoint-url https://minio-api.michaelschiemer.de s3 mb s3://my-bucket
# Upload file
aws --endpoint-url https://minio-api.michaelschiemer.de s3 cp file.txt s3://my-bucket/
# Download file
aws --endpoint-url https://minio-api.michaelschiemer.de s3 cp s3://my-bucket/file.txt ./
# List objects
aws --endpoint-url https://minio-api.michaelschiemer.de s3 ls s3://my-bucket/
# Delete object
aws --endpoint-url https://minio-api.michaelschiemer.de s3 rm s3://my-bucket/file.txt
# Delete bucket
aws --endpoint-url https://minio-api.michaelschiemer.de s3 rb s3://my-bucket
```
#### Using MinIO Client (mc)
```bash
# Install MinIO Client
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# Configure alias
mc alias set minio https://minio-api.michaelschiemer.de minioadmin <MINIO_ROOT_PASSWORD>
# Test connection
mc admin info minio
# List buckets
mc ls minio
# Create bucket
mc mb minio/my-bucket
# Upload file
mc cp file.txt minio/my-bucket/
# Download file
mc cp minio/my-bucket/file.txt ./
# List objects
mc ls minio/my-bucket/
# Remove object
mc rm minio/my-bucket/file.txt
# Remove bucket
mc rb minio/my-bucket
```
#### Using cURL
```bash
# List buckets
curl -X GET https://minio-api.michaelschiemer.de \
-u "minioadmin:<MINIO_ROOT_PASSWORD>"
# Upload file (using presigned URL from Console or SDK)
```
### Programmatic Access
#### PHP (Using AWS SDK)
```php
use Aws\S3\S3Client;
use Aws\Exception\AwsException;
$s3Client = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
'endpoint' => 'https://minio-api.michaelschiemer.de',
'use_path_style_endpoint' => true,
'credentials' => [
'key' => 'minioadmin',
'secret' => '<MINIO_ROOT_PASSWORD>',
],
]);
// Create bucket
$s3Client->createBucket(['Bucket' => 'my-bucket']);
// Upload file
$s3Client->putObject([
'Bucket' => 'my-bucket',
'Key' => 'file.txt',
'Body' => fopen('/path/to/file.txt', 'r'),
]);
// Download file
$result = $s3Client->getObject([
'Bucket' => 'my-bucket',
'Key' => 'file.txt',
]);
echo $result['Body'];
```
#### JavaScript/Node.js
```javascript
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
endpoint: 'https://minio-api.michaelschiemer.de',
accessKeyId: 'minioadmin',
secretAccessKey: '<MINIO_ROOT_PASSWORD>',
s3ForcePathStyle: true,
signatureVersion: 'v4',
});
// Create bucket
s3.createBucket({ Bucket: 'my-bucket' }, (err, data) => {
if (err) console.error(err);
else console.log('Bucket created');
});
// Upload file
const params = {
Bucket: 'my-bucket',
Key: 'file.txt',
Body: require('fs').createReadStream('/path/to/file.txt'),
};
s3.upload(params, (err, data) => {
if (err) console.error(err);
else console.log('File uploaded:', data.Location);
});
```
#### Python (boto3)
```python
import boto3
from botocore.client import Config
s3_client = boto3.client(
's3',
endpoint_url='https://minio-api.michaelschiemer.de',
aws_access_key_id='minioadmin',
aws_secret_access_key='<MINIO_ROOT_PASSWORD>',
config=Config(signature_version='s3v4'),
region_name='us-east-1'
)
# Create bucket
s3_client.create_bucket(Bucket='my-bucket')
# Upload file
s3_client.upload_file('/path/to/file.txt', 'my-bucket', 'file.txt')
# Download file
s3_client.download_file('my-bucket', 'file.txt', '/path/to/downloaded.txt')
# List objects
response = s3_client.list_objects_v2(Bucket='my-bucket')
for obj in response.get('Contents', []):
print(obj['Key'])
```
## User Management
### Create Access Keys via Console
1. Login to Console: https://minio.michaelschiemer.de
2. Navigate to "Access Keys" → "Create Access Key"
3. Assign policies (read-only, read-write, admin)
4. Save Access Key and Secret Key (only shown once!)
### Create Access Keys via mc CLI
```bash
# Create new user
mc admin user add minio myuser mypassword
# Create access key for user
mc admin user svcacct add minio myuser --name my-access-key
# Output shows Access Key and Secret Key
```
### Policy Management
```bash
# List policies
mc admin policy list minio
# Create custom policy
mc admin policy add minio readwrite-policy /path/to/policy.json
# Assign policy to user
mc admin policy set minio readwrite-policy user=myuser
# Remove policy from user
mc admin policy remove minio readwrite-policy user=myuser
```
**Example Policy** (`policy.json`):
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": ["arn:aws:s3:::my-bucket/*"]
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::my-bucket"]
}
]
}
```
## Integration with Application Stack
### Environment Variables
Add to `application/.env`:
```env
# MinIO Object Storage
MINIO_ENDPOINT=https://minio-api.michaelschiemer.de
MINIO_ACCESS_KEY=<access-key>
MINIO_SECRET_KEY=<secret-key>
MINIO_BUCKET=<bucket-name>
MINIO_USE_SSL=true
MINIO_REGION=us-east-1
```
### PHP Integration
```php
// Use AWS SDK or MinIO PHP SDK
use Aws\S3\S3Client;
$s3Client = new S3Client([
'version' => 'latest',
'region' => $_ENV['MINIO_REGION'],
'endpoint' => $_ENV['MINIO_ENDPOINT'],
'use_path_style_endpoint' => true,
'credentials' => [
'key' => $_ENV['MINIO_ACCESS_KEY'],
'secret' => $_ENV['MINIO_SECRET_KEY'],
],
]);
```
## Backup & Recovery
### Manual Backup
```bash
#!/bin/bash
# backup-minio.sh
BACKUP_DIR="/backups/minio"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# Backup MinIO data
docker run --rm \
-v minio-data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/minio-data-$DATE.tar.gz -C /data .
echo "Backup completed: $BACKUP_DIR/minio-data-$DATE.tar.gz"
```
### Restore from Backup
```bash
# Stop MinIO
docker compose down
# Restore data
docker run --rm \
-v minio-data:/data \
-v /backups/minio:/backup \
alpine tar xzf /backup/minio-data-YYYYMMDD_HHMMSS.tar.gz -C /data
# Start MinIO
docker compose up -d
```
### Automated Backups
Add to crontab:
```bash
# Daily backup at 3 AM
0 3 * * * /path/to/backup-minio.sh
# Keep only last 30 days
0 4 * * * find /backups/minio -type f -mtime +30 -delete
```
### Bucket-Level Backup (Recommended)
Use MinIO's built-in replication or external tools:
```bash
# Sync bucket to external storage
mc mirror minio/my-bucket /backup/my-bucket/
# Or sync to another MinIO instance
mc mirror minio/my-bucket remote-minio/my-bucket/
```
## Monitoring
### Health Checks
```bash
# Check MinIO health
docker compose ps
# API health endpoint
curl -f https://minio-api.michaelschiemer.de/minio/health/live
# Check storage usage
docker exec minio du -sh /data
```
### Logs
```bash
# View logs
docker compose logs -f
# Check for errors
docker compose logs minio | grep -i error
# Monitor API access
docker compose logs -f minio | grep "GET /"
```
### Storage Statistics
```bash
# Check volume size
docker volume inspect minio-data
# Check disk usage
docker system df -v | grep minio
# List buckets via mc
mc ls minio
```
### Metrics (via mc)
```bash
# Server info
mc admin info minio
# Service status
mc admin service status minio
# Trace operations
mc admin trace minio
```
## Performance Tuning
### MinIO Configuration
For high-traffic scenarios, edit `docker-compose.yml`:
```yaml
environment:
# Increase concurrent operations
- MINIO_CACHE_DRIVES=/mnt/cache1,/mnt/cache2
- MINIO_CACHE_QUOTA=80
- MINIO_CACHE_AFTER=0
- MINIO_CACHE_WATERMARK_LOW=70
- MINIO_CACHE_WATERMARK_HIGH=90
```
### Storage Optimization
```bash
# Monitor storage growth
du -sh /var/lib/docker/volumes/minio-data/
# Enable compression (handled automatically by MinIO)
# Set bucket quotas via Console or mc
mc admin quota set minio/my-bucket --hard 100GB
```
## Troubleshooting
### Cannot Access Console
```bash
# Check service is running
docker compose ps
# Check Traefik routing
docker exec traefik cat /etc/traefik/traefik.yml
# Check network
docker network inspect traefik-public | grep minio
# Test from server
curl -k https://localhost:9001
```
### Authentication Failed
```bash
# Verify environment variables
docker exec minio env | grep MINIO_ROOT
# Check logs
docker compose logs minio | grep -i auth
# Reset root password (stop container, remove volume, restart)
```
### SSL Certificate Issues
```bash
# Verify Traefik certificate
docker exec traefik cat /acme.json | grep minio
# Test SSL
openssl s_client -connect minio-api.michaelschiemer.de:443 \
-servername minio-api.michaelschiemer.de < /dev/null
```
### Storage Issues
```bash
# Check volume mount
docker exec minio df -h /data
# Check for corrupted data
docker exec minio find /data -type f -name "*.json" | head
# Check disk space
df -h /var/lib/docker/volumes/minio-data/
```
### API Connection Errors
```bash
# Verify endpoint URL
curl -I https://minio-api.michaelschiemer.de/minio/health/live
# Test with credentials
curl -u minioadmin:<password> https://minio-api.michaelschiemer.de
# Check CORS settings (if needed for web apps)
mc admin config set minio api cors_allow_origin "https://yourdomain.com"
```
## Security
### Security Best Practices
1. **Strong Credentials**: Use strong passwords for root user and access keys
2. **Change Default Root User**: Don't use `minioadmin` in production
3. **SSL Only**: Always use HTTPS (enforced via Traefik)
4. **Access Key Rotation**: Regularly rotate access keys
5. **Policy-Based Access**: Use IAM policies to limit permissions
6. **Bucket Policies**: Configure bucket-level policies
7. **Audit Logging**: Enable audit logging for compliance
8. **Encryption**: Enable encryption at rest and in transit
### Enable Audit Logging
```bash
# Configure audit logging
mc admin config set minio audit_webhook \
endpoint=https://log-service.example.com/webhook \
auth_token=<token>
```
### Enable Encryption
```bash
# Set encryption keys
mc admin config set minio encryption \
sse-s3 enable \
kms endpoint=https://kms.example.com \
kms-key-id=<key-id>
```
### Update Stack
```bash
# Pull latest images
docker compose pull
# Recreate containers
docker compose up -d
# Verify
docker compose ps
```
### Security Headers
Security headers are applied via Traefik's `default-chain@file` middleware:
- HSTS
- Content-Type Nosniff
- XSS Protection
- Frame Deny
## Additional Resources
- **MinIO Documentation**: https://min.io/docs/
- **S3 API Compatibility**: https://min.io/docs/minio/linux/reference/minio-mc/mc.html
- **MinIO Client (mc)**: https://min.io/docs/minio/linux/reference/minio-mc.html
- **MinIO Erasure Coding**: https://min.io/docs/minio/linux/operations/concepts/erasure-coding.html
- **MinIO Security**: https://min.io/docs/minio/linux/operations/security.html

View File

@@ -0,0 +1,50 @@
services:
minio:
image: minio/minio:latest
container_name: minio
restart: unless-stopped
networks:
- traefik-public
environment:
- TZ=Europe/Berlin
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-minioadmin}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
command: server /data --console-address ":9001"
volumes:
- minio-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
labels:
- "traefik.enable=true"
# API Router (S3-compatible endpoint)
- "traefik.http.routers.minio-api.rule=Host(`${MINIO_API_DOMAIN:-minio-api.michaelschiemer.de}`)"
- "traefik.http.routers.minio-api.entrypoints=websecure"
- "traefik.http.routers.minio-api.tls=true"
- "traefik.http.routers.minio-api.tls.certresolver=letsencrypt"
- "traefik.http.routers.minio-api.service=minio-api"
- "traefik.http.routers.minio-api.middlewares=default-chain@file"
- "traefik.http.services.minio-api.loadbalancer.server.port=9000"
# Console Router (Web UI)
- "traefik.http.routers.minio-console.rule=Host(`${MINIO_CONSOLE_DOMAIN:-minio.michaelschiemer.de}`)"
- "traefik.http.routers.minio-console.entrypoints=websecure"
- "traefik.http.routers.minio-console.tls=true"
- "traefik.http.routers.minio-console.tls.certresolver=letsencrypt"
- "traefik.http.routers.minio-console.service=minio-console"
- "traefik.http.routers.minio-console.middlewares=default-chain@file"
- "traefik.http.services.minio-console.loadbalancer.server.port=9001"
volumes:
minio-data:
name: minio-data
networks:
traefik-public:
external: true

View File

@@ -1,5 +1,3 @@
version: '3.8'
services:
# PostgreSQL Database
postgres:

View File

@@ -1,5 +1,3 @@
version: '3.8'
services:
registry:
image: registry:2.8
@@ -8,7 +6,7 @@ services:
networks:
- traefik-public
ports:
- "127.0.0.1:5000:5000"
- "0.0.0.0:5000:5000"
environment:
- TZ=Europe/Berlin
- REGISTRY_STORAGE_DELETE_ENABLED=true
@@ -39,7 +37,7 @@ services:
# Middleware
- "traefik.http.routers.registry.middlewares=default-chain@file"
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:5000/v2/"]
test: ["CMD-SHELL", "wget --spider -q --header='Authorization: Basic YWRtaW46cmVnaXN0cnktc2VjdXJlLXBhc3N3b3JkLTIwMjU=' http://localhost:5000/v2/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -1,5 +1,3 @@
version: '3.8'
services:
traefik:
image: traefik:v3.0