From a371a33682312865c1c321cf2d4c31c59b46c75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Sat, 25 Oct 2025 13:52:01 +0200 Subject: [PATCH] a new start --- .gitignore | 2 + README.md | 1 + arty.yml | 77 +++++++++++++++++++++++++ compose.yaml | 11 ++++ core/compose.yaml | 49 ++++++++++++++++ core/postgres/init/01-init-databases.sh | 38 ++++++++++++ gotify/compose.yaml | 32 ++++++++++ proxy/compose.yaml | 59 +++++++++++++++++++ track/compose.yaml | 44 ++++++++++++++ vpn/compose.yaml | 67 +++++++++++++++++++++ 10 files changed, 380 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 arty.yml create mode 100644 compose.yaml create mode 100644 core/compose.yaml create mode 100644 core/postgres/init/01-init-databases.sh create mode 100644 gotify/compose.yaml create mode 100644 proxy/compose.yaml create mode 100644 track/compose.yaml create mode 100644 vpn/compose.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78b3df5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +*.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..48ed70b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# docker-compose diff --git a/arty.yml b/arty.yml new file mode 100644 index 0000000..54afc2d --- /dev/null +++ b/arty.yml @@ -0,0 +1,77 @@ +name: "docker.compose" +version: "1.0.0" +description: "Valknar's Stacks" +author: "valknar@pivoine.art" +license: "private" + +envs: + default: + # Common + ADMIN_EMAIL: valknar@pivoine.art + NETWORK_NAME: pivoine + TIMEZONE: Europe/Berlin + # Core + CORE_COMPOSE_PROJECT_NAME: core + CORE_DB_HOST: postgres + CORE_DB_PORT: 5432 + CORE_REDIS_HOST: redis + CORE_REDIS_PORT: 6379 + CORE_REDIS_IMAGE: redis:7-alpine + CORE_POSTGRES_IMAGE: postgres:16-alpine + # VPN + VPN_TRAEFIK_ENABLED: true + VPN_COMPOSE_PROJECT_NAME: vpn + VPN_DOCKER_IMAGE: ghcr.io/wg-easy/wg-easy:latest + VPN_TRAEFIK_HOST: vpn.pivoine.art + # Track + TRACK_TRAEFIK_ENABLED: true + TRACK_COMPOSE_PROJECT_NAME: track + TRACK_DOCKER_IMAGE: ghcr.io/umami-software/umami:postgresql-latest + TRACK_TRAEFIK_HOST: track.pivoine.art + TRACK_DB_NAME: umami + # Blog + BLOG_TRAEFIK_ENABLED: true + BLOG_COMPOSE_PROJECT_NAME: blog + BLOG_DOCKER_IMAGE: joseluisq/static-web-server:latest + BLOG_TRAEFIK_HOST: pivoine.art + # Sexy + SEXY_TRAEFIK_ENABLED: true + SEXY_COMPOSE_PROJECT_NAME: sexy + SEXY_TRAEFIK_HOST: sexy.pivoine.art + SEXY_DIRECTUS_IMAGE: directus/directus:11.12.0 + SEXY_FRONTEND_IMAGE: node:22 + SEXY_DB_NAME: directus + SEXY_PUBLIC_URL: https://sexy.pivoine.art/api + SEXY_CORS_ORIGIN: https://sexy.pivoine.art + SEXY_SESSION_COOKIE_DOMAIN: sexy.pivoine.art + SEXY_CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC: https://sexy.pivoine.art + SEXY_USER_REGISTER_URL_ALLOW_LIST: https://sexy.pivoine.art/signup/verify + SEXY_PASSWORD_RESET_URL_ALLOW_LIST: https://sexy.pivoine.art/password/reset + # Gotify + GOTIFY_TRAEFIK_ENABLED: true + GOTIFY_COMPOSE_PROJECT_NAME: track + GOTIFY_IMAGE: gotify/server:latest + GOTIFY_TRAEFIK_HOST: gotify.pivoine.art + # Proxy + PROXY_COMPOSE_PROJECT_NAME: proxy + PROXY_DOCKER_IMAGE: traefik:latest + +scripts: + config: docker compose config + up: docker compose up -d + down: docker compose down + logs: docker compose logs -f + sync: rsync -avzhe ssh ./.env root@vps:~/Projects/docker.compose/ + db/dump: | + PGPASSWORD="${DB_PASSWORD}" pg_dump -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d "${DB_NAME}" > sexy/directus.sql \ + docker cp "sexy/directus.yaml" "sexy_api:/directus/directus.yaml" \ + docker exec "sexy_api" npx directus schema apply /directus/directus.yaml \ + db/import: | + PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d postgres -c \ + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DB_NAME}' AND pid <> pg_backend_pid();" \ + PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d postgres -c "DROP DATABASE IF EXISTS ${DB_NAME};" \ + PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d postgres -c "CREATE DATABASE ${DB_NAME};" \ + PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d "${DB_NAME}" -f "sexy/directus.sql" \ + docker exec "sexy_api" npx directus schema snapshot "/directus/directus.yaml" \ + docker cp "sexy_api:/directus/directus.yaml" "sexy/directus.yaml" + net: docker network create "$NETWORK_NAME" diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..0ae5743 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,11 @@ +name: falcon +include: + - core/compose.yaml + - gotify/compose.yaml + - track/compose.yaml + - proxy/compose.yaml + +networks: + compose_network: + name: ${NETWORK_NAME:-falcon_network} + external: true diff --git a/core/compose.yaml b/core/compose.yaml new file mode 100644 index 0000000..9f5b7fb --- /dev/null +++ b/core/compose.yaml @@ -0,0 +1,49 @@ +services: + # PostgreSQL - Central Database + postgres: + image: ${CORE_POSTGRES_IMAGE:-postgres:16-alpine} + container_name: ${CORE_COMPOSE_PROJECT_NAME}_postgres + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + # Performance tuning + POSTGRES_MAX_CONNECTIONS: ${CORE_POSTGRES_MAX_CONNECTIONS:-100} + POSTGRES_SHARED_BUFFERS: ${CORE_POSTGRES_SHARED_BUFFERS:-256MB} + volumes: + - postgres_data:/var/lib/postgresql/data + - ./postgres/init:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + networks: + - compose_network + + redis: + image: ${CORE_REDIS_IMAGE:-redis:7-alpine} + container_name: ${CORE_COMPOSE_PROJECT_NAME}_redis + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + + networks: + - compose_network + +volumes: + postgres_data: + name: ${CORE_COMPOSE_PROJECT_NAME}_postgres_data + redis_data: + name: ${CORE_COMPOSE_PROJECT_NAME}_redis_data diff --git a/core/postgres/init/01-init-databases.sh b/core/postgres/init/01-init-databases.sh new file mode 100644 index 0000000..0b7cd06 --- /dev/null +++ b/core/postgres/init/01-init-databases.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +# PostgreSQL initialization script for compose core stack +# This script runs on first database initialization +# Creates all databases required by compose.sh stacks + +echo "Starting compose database initialization..." + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + -- Create databases for compose services + -- Main application database + SELECT 'CREATE DATABASE directus' + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'directus')\gexec + + -- n8n workflow database + SELECT 'CREATE DATABASE umami' + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'umami')\gexec + + -- Grant privileges to all databases + GRANT ALL PRIVILEGES ON DATABASE directus TO $POSTGRES_USER; + GRANT ALL PRIVILEGES ON DATABASE umami TO $POSTGRES_USER; + + -- Log success + SELECT 'Compose databases initialized:' AS status; + SELECT datname FROM pg_database + WHERE datname IN ('directus', 'umami') + ORDER BY datname; +EOSQL + +echo "" +echo "✓ PostgreSQL initialization completed" +echo "✓ All compose databases created successfully" +echo "" +echo "Databases available:" +echo " • directus - Sexy application database" +echo " • umami - Tracking database" +echo "" diff --git a/gotify/compose.yaml b/gotify/compose.yaml new file mode 100644 index 0000000..758cf99 --- /dev/null +++ b/gotify/compose.yaml @@ -0,0 +1,32 @@ +services: + gotify: + image: ${GOTIFY_IMAGE} + container_name: ${GOTIFY_COMPOSE_PROJECT_NAME}_gotify + restart: unless-stopped + volumes: + - gotify_data:/app/data + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + GOTIFY_DEFAULTUSER_NAME: ${GOTIFY_DEFAULTUSER_NAME} + GOTIFY_DEFAULTUSER_PASS: ${GOTIFY_DEFAULTUSER_PASS} + ports: + - "${GOTIFY_PORT:-8085}:80" + networks: + - compose_network + labels: + - 'traefik.enable=${GOTIFY_TRAEFIK_ENABLED}' + - 'traefik.http.middlewares.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web.middlewares=${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-redirect-web-secure' + - 'traefik.http.routers.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web.rule=Host(`${GOTIFY_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web.entrypoints=web' + - 'traefik.http.routers.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web-secure.rule=Host(`${GOTIFY_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web-secure-compress.compress=true' + - 'traefik.http.routers.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web-secure.middlewares=${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web-secure-compress' + - 'traefik.http.services.${GOTIFY_COMPOSE_PROJECT_NAME}-gotify-web-secure.loadbalancer.server.port=80' + - 'traefik.docker.network=compose_network' + +volumes: + gotify_data: + name: ${GOTIFY_COMPOSE_PROJECT_NAME}_gotify_data diff --git a/proxy/compose.yaml b/proxy/compose.yaml new file mode 100644 index 0000000..a1e5ad5 --- /dev/null +++ b/proxy/compose.yaml @@ -0,0 +1,59 @@ +services: + traefik: + image: ${PROXY_DOCKER_IMAGE} + container_name: ${PROXY_COMPOSE_PROJECT_NAME}_app + restart: unless-stopped + command: + # API & Dashboard + - '--api.dashboard=false' + - '--api.insecure=false' + + # Logging + - '--log.level=${PROXY_LOG_LEVEL:-INFO}' + - '--accesslog=true' + + # Global + - '--global.sendAnonymousUsage=false' + - '--global.checkNewVersion=true' + + # Docker Provider + - '--providers.docker=true' + - '--providers.docker.exposedbydefault=false' + - '--providers.docker.network=compose_network' + + # File Provider for dynamic configuration + # - '--providers.file.directory=/etc/traefik/dynamic' + # - '--providers.file.watch=true' + + # Entrypoints + - '--entrypoints.web.address=:${PROXY_PORT_HTTP:-80}' + - '--entrypoints.web-secure.address=:${PROXY_PORT_HTTPS:-443}' + + # Global HTTP to HTTPS redirect + - '--entrypoints.web.http.redirections.entryPoint.to=web-secure' + - '--entrypoints.web.http.redirections.entryPoint.scheme=https' + - '--entrypoints.web.http.redirections.entryPoint.permanent=true' + + # Let's Encrypt + - '--certificatesresolvers.resolver.acme.tlschallenge=true' + - '--certificatesresolvers.resolver.acme.email=${ADMIN_EMAIL}' + - '--certificatesresolvers.resolver.acme.storage=/letsencrypt/acme.json' + + healthcheck: + test: ["CMD", "traefik", "healthcheck", "--ping"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + networks: + - compose_network + + ports: + - "${PROXY_PORT_HTTP:-80}:80" + - "${PROXY_PORT_HTTPS:-443}:443" + + volumes: + - /var/local/data/traefik/letsencrypt:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./dynamic:/etc/traefik/dynamic:ro diff --git a/track/compose.yaml b/track/compose.yaml new file mode 100644 index 0000000..3ecdf2a --- /dev/null +++ b/track/compose.yaml @@ -0,0 +1,44 @@ +services: + umami: + image: ${TRACK_DOCKER_IMAGE} + container_name: ${TRACK_COMPOSE_PROJECT_NAME}_app + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + + # Database Configuration + DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@${CORE_DB_HOST}:${CORE_DB_PORT}/${TRACK_DB_NAME} + DATABASE_TYPE: postgresql + + # Application Secret + APP_SECRET: ${TRACK_APP_SECRET} + + # Redis Cache Integration + REDIS_URL: redis://${CORE_REDIS_HOST}:${CORE_REDIS_PORT} + CACHE_ENABLED: true + + + networks: + - compose_network + + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 40s + + labels: + # Traefik Configuration + - 'traefik.enable=${TRACK_TRAEFIK_ENABLED:-true}' + + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${TRACK_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${TRACK_COMPOSE_PROJECT_NAME}-web.middlewares=${TRACK_COMPOSE_PROJECT_NAME}-redirect-web-secure' + - 'traefik.http.routers.${TRACK_COMPOSE_PROJECT_NAME}-web.rule=Host(`${TRACK_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${TRACK_COMPOSE_PROJECT_NAME}-web.entrypoints=web' + - 'traefik.http.routers.${TRACK_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${TRACK_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${TRACK_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${TRACK_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure' + - 'traefik.http.services.${TRACK_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=3000' + - 'traefik.docker.network=compose_network' diff --git a/vpn/compose.yaml b/vpn/compose.yaml new file mode 100644 index 0000000..a63fc24 --- /dev/null +++ b/vpn/compose.yaml @@ -0,0 +1,67 @@ +services: + wg-easy: + image: ${VPN_DOCKER_IMAGE} + container_name: ${VPN_COMPOSE_PROJECT_NAME}_app + restart: unless-stopped + volumes: + - etc_wireguard:/etc/wireguard + - /lib/modules:/lib/modules:ro + ports: + - "${VPN_WG_PORT:-51820}:51820/udp" + - "${VPN_APP_PORT:-51821}:51821/tcp" + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + # - net.ipv6.conf.all.disable_ipv6=0 + # - net.ipv6.conf.all.forwarding=1 + # - net.ipv6.conf.default.forwarding=1 + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + WG_HOST: ${VPN_WG_HOST} + WG_PORT: ${VPN_WG_PORT} + PORT: ${VPN_APP_PORT} + WG_DEVICE: eth0 + PASSWORD: ${VPN_PASSWORD} + LANG: ${VPN_LANG:-en} + UI_TRAFFIC_STATS: ${VPN_UI_TRAFFIC_STATS:-true} + UI_CHART_TYPE: ${VPN_UI_CHART_TYPE:-0} + WG_ALLOWED_IPS: ${VPN_WG_ALLOWED_IPS:-0.0.0.0/0, ::/0} + WG_DEFAULT_ADDRESS: ${VPN_WG_DEFAULT_ADDRESS:-10.8.0.x} + WG_DEFAULT_DNS: ${VPN_WG_DEFAULT_DNS:-1.1.1.1} + WG_PERSISTENT_KEEPALIVE: ${VPN_WG_PERSISTENT_KEEPALIVE:-25} + WG_MTU: ${VPN_WG_MTU:-1420} + networks: + wg: + ipv4_address: 10.42.42.42 + ipv6_address: fdcc:ad94:bacf:61a3::2a + compose_network: + labels: + - 'traefik.enable=${VPN_TRAEFIK_ENABLED:-true}' + - 'traefik.http.middlewares.${VPN_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${VPN_COMPOSE_PROJECT_NAME}-web.middlewares=${VPN_COMPOSE_PROJECT_NAME}-redirect-web-secure' + - 'traefik.http.routers.${VPN_COMPOSE_PROJECT_NAME}-web.rule=Host(`${VPN_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${VPN_COMPOSE_PROJECT_NAME}-web.entrypoints=web' + - 'traefik.http.routers.${VPN_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${VPN_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${VPN_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${VPN_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${VPN_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true' + - 'traefik.http.routers.${VPN_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${VPN_COMPOSE_PROJECT_NAME}-web-secure-compress' + - 'traefik.http.services.${VPN_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=51821' + - 'traefik.docker.network=compose_network' + +volumes: + etc_wireguard: + name: ${VPN_COMPOSE_PROJECT_NAME}_etc_wireguard + +networks: + wg: + driver: bridge + enable_ipv6: true + ipam: + driver: default + config: + - subnet: 10.42.42.0/24 + - subnet: fdcc:ad94:bacf:61a3::/64