From c19d43f23d8f1368367ba4e859dc9b8cc028bbfd Mon Sep 17 00:00:00 2001 From: "ivan.stasevich" Date: Fri, 29 May 2026 13:13:16 +0300 Subject: [PATCH 1/5] feat: add docker registry push --- .env.example | 51 -------- .github/workflows/dockerhub.yml | 47 +++++++ .secrets.baseline | 25 +--- Makefile | 24 +++- docker-compose-prod.yml | 15 --- docker-compose.dev.yml | 85 ++++++++++++ docker-compose.example.yml | 72 +++++++++++ docker-compose.observability.yml | 19 +++ docker-compose.prod.yml | 6 + docker-compose.yml | 149 ---------------------- docker/{fastapi.Dockerfile => Dockerfile} | 4 - docker/entrypoint-dev.sh | 2 +- docker/entrypoint.sh | 14 +- fastid/admin/app.py | 1 + fastid/auth/models.py | 9 +- fastid/core/app.py | 12 ++ fastid/core/config.py | 2 + fastid/notify/clients/dependencies.py | 11 +- fastid/notify/clients/smtp.py | 7 +- fastid/notify/config.py | 21 ++- fastid/notify/exceptions.py | 12 ++ fastid/notify/use_cases.py | 12 +- fastid/notify/utils.py | 2 +- 23 files changed, 332 insertions(+), 270 deletions(-) create mode 100644 .github/workflows/dockerhub.yml delete mode 100644 docker-compose-prod.yml create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.example.yml create mode 100644 docker-compose.observability.yml create mode 100644 docker-compose.prod.yml delete mode 100644 docker-compose.yml rename docker/{fastapi.Dockerfile => Dockerfile} (95%) create mode 100644 fastid/core/app.py diff --git a/.env.example b/.env.example index 97a17f4..2aa8dd5 100644 --- a/.env.example +++ b/.env.example @@ -1,53 +1,2 @@ -# Main -#MAIN_DISCOVERY_NAME="fastid" -#MAIN_TITLE="FastID" -#MAIN_ENV="local" -MAIN_BASE_URL="http://localhost:8012" - -# Infrastructure -DB_URL="postgresql+asyncpg://postgres:changethis@fastid-db:5432/app" -REDIS_URL="redis://default:changethis@fastid-redis:6379/0" - -# Notifications -NOTIFY_FROM_NAME="FastID" -NOTIFY_SMTP_HOST="smtp.gmail.com" -NOTIFY_SMTP_PORT=465 -NOTIFY_SMTP_USERNAME=... -NOTIFY_SMTP_PASSWORD=... - -# CORS -#CORS_ORIGINS='["*"]' - -# Auth -#AUTH_JWT_PRIVATE_KEY="certs/jwt-private.pem" -#AUTH_JWT_PUBLIC_KEY="certs/jwt-public.pem" - -# Admin -#ADMIN_USERNAME="admin" -#ADMIN_PASSWORD="admin" - -# Telegram -#TELEGRAM_OAUTH_ENABLED=1 -#TELEGRAM_NOTITIFCATION_ENABLED=1 -#TELEGRAM_BOT_TOKEN=... - -# Google -#GOOGLE_ENABLED=1 -#GOOGLE_CLIENT_ID=... -#GOOGLE_CLIENT_SECRET=... - -# Yandex -#YANDEX_ENABLED=1 -#YANDEX_CLIENT_ID=... -#YANDEX_CLIENT_SECRET=... - -# Plugins -#OBS_ENABLED=1 -#OBS_TEMPO_URL="http://tempo:4317" - -# Docker environment -POSTGRES_USER="postgres" POSTGRES_PASSWORD="changethis" -POSTGRES_DB="app" - REDIS_PASSWORD="changethis" diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml new file mode 100644 index 0000000..a57a7e4 --- /dev/null +++ b/.github/workflows/dockerhub.yml @@ -0,0 +1,47 @@ +name: Push to Docker Hub + +on: + workflow_run: + workflows: [Run all tests] + types: [completed] + +env: + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') }} + type=semver,pattern={{version}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.secrets.baseline b/.secrets.baseline index 1eb0b11..943aa6a 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -134,33 +134,12 @@ ], "results": { ".env.example": [ - { - "type": "Basic Auth Credentials", - "filename": ".env.example", - "hashed_secret": "cdb0e76c1a69873cbdcdbe0a142d56c023dc9f22", - "is_verified": false, - "line_number": 8 - }, - { - "type": "Secret Keyword", - "filename": ".env.example", - "hashed_secret": "5ea5a520483e6ef900630647ce75e3d80467484d", - "is_verified": false, - "line_number": 22 - }, - { - "type": "Secret Keyword", - "filename": ".env.example", - "hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997", - "is_verified": false, - "line_number": 27 - }, { "type": "Secret Keyword", "filename": ".env.example", "hashed_secret": "cdb0e76c1a69873cbdcdbe0a142d56c023dc9f22", "is_verified": false, - "line_number": 50 + "line_number": 1 } ], "alembic.ini": [ @@ -182,5 +161,5 @@ } ] }, - "generated_at": "2026-01-21T08:53:17Z" + "generated_at": "2026-05-29T10:11:37Z" } diff --git a/Makefile b/Makefile index ed0a9f5..95f640a 100644 --- a/Makefile +++ b/Makefile @@ -6,15 +6,27 @@ certs: .PHONY: deps deps: - docker compose up fastid-db fastid-redis -d + docker compose -f docker-compose.dev.yml postgres redis up -d --build --remove-orphans --wait .PHONY: up up: - docker compose up --build --remove-orphans --wait + docker compose -f docker-compose.dev.yml up --build --remove-orphans --wait + +.PHONY: up-obs +up-obs: + docker compose -f docker-compose.dev.yml -f docker-compose.observability.yml up --build --remove-orphans --wait .PHONY: up-prod up-prod: - docker compose -f docker-compose.yml -f docker-compose-prod.yml up --build --remove-orphans --wait + docker compose -f docker-compose.dev.yml -f docker-compose.prod.yml up --build --remove-orphans --wait + +.PHONY: up-prod-obs +up-prod-obs: + docker compose -f docker-compose.dev.yml -f docker-compose.prod.yml -f docker-compose.observability.yml up --build --remove-orphans --wait + +.PHONY: up-example +up-example: + docker compose -f docker-compose.example.yml up --build --remove-orphans --wait .PHONY: test test: @@ -30,15 +42,15 @@ testcov: .PHONY: stop stop: - docker compose stop + docker compose -f docker-compose.dev.yml stop .PHONY: down down: - docker compose down + docker compose -f docker-compose.dev.yml down .PHONY: restart restart: - docker compose restart + docker compose -f docker-compose.dev.yml restart .PHONY: lint lint: diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml deleted file mode 100644 index 8c7b40d..0000000 --- a/docker-compose-prod.yml +++ /dev/null @@ -1,15 +0,0 @@ -services: - fastid-api: - build: - target: prod - command: "gunicorn -w 1 -k fastid.core.workers.MyUvicornWorker \"fastid.api.app:api_app\" -b 0.0.0.0:8000" - - fastid-frontend: - build: - target: prod - command: "gunicorn -w 1 -k fastid.core.workers.MyUvicornWorker \"fastid.frontend.app:frontend_app\" -b 0.0.0.0:3000" - - fastid-admin: - build: - target: prod - command: "gunicorn -w 1 -k fastid.core.workers.MyUvicornWorker \"fastid.admin.app:admin_app\" -b 0.0.0.0:4000" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..ea261c0 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,85 @@ +services: + postgres: + env_file: + - .env + image: postgres:16-alpine + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?database password required} + POSTGRES_USER: ${POSTGRES_USER:-fastid} + POSTGRES_DB: ${POSTGRES_DB:-fastid} + volumes: + - pg_data:/var/lib/postgresql/data + ports: + - "5412:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s + + redis: + env_file: + - .env + image: redis:7-alpine + command: redis-server --requirepass ${REDIS_PASSWORD:?redis password required} + ports: + - "6312:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + mailpit: + image: axllent/mailpit:v1.30.1 + ports: + - "8025:8025" + - "1025:1025" + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8025/readyz"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + + fastid-app: + build: + context: . + dockerfile: docker/Dockerfile + target: dev + env_file: .env + ports: + - "8012:8000" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + mailpit: + condition: service_healthy + + environment: + DB_URL: ${DB_URL?-postgresql+asyncpg://${POSTGRES_USER:-fastid}:${POSTGRES_PASSWORD:?database password required}@postgres:5432/${POSTGRES_DB:-fastid}} + REDIS_URL: ${REDIS_URL?-redis://:${REDIS_PASSWORD:?redis password required}@redis:6379/0} + NOTIFY_SMTP_ENABLED: ${NOTIFY_SMTP_ENABLED:-true} + NOTIFY_SMTP_HOST: ${NOTIFY_SMTP_HOST:-mailpit} + NOTIFY_SMTP_PORT: ${NOTIFY_SMTP_PORT:-1025} + + volumes: + - "./migrations:/opt/fastid/migrations" + - "./fastid:/opt/fastid/fastid" + - "./templates:/opt/fastid/templates" + - "./static:/opt/fastid/static" + - "./certs:/opt/fastid/certs" + + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/readiness" ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + pg_data: diff --git a/docker-compose.example.yml b/docker-compose.example.yml new file mode 100644 index 0000000..87c32a3 --- /dev/null +++ b/docker-compose.example.yml @@ -0,0 +1,72 @@ +services: + postgres: + env_file: + - .env + image: postgres:16-alpine + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?database password required} + POSTGRES_USER: ${POSTGRES_USER:-fastid} + POSTGRES_DB: ${POSTGRES_DB:-fastid} + volumes: + - pg_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-fastid} -d ${POSTGRES_DB:-fastid}"] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s + + redis: + env_file: + - .env + image: redis:7-alpine + command: redis-server --requirepass ${REDIS_PASSWORD:?redis password required} + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + mailpit: + image: axllent/mailpit:v1.30.1 + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8025/readyz"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + + fastid-app: + env_file: + - .env + image: everysoftware/fastid:latest + ports: + - "8012:8000" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + mailpit: + condition: service_healthy + + environment: + DB_URL: ${DB_URL?-postgresql+asyncpg://${POSTGRES_USER:-fastid}:${POSTGRES_PASSWORD:?database password required}@postgres:5432/${POSTGRES_DB:-fastid}} + REDIS_URL: ${REDIS_URL?-redis://:${REDIS_PASSWORD:?redis password required}@redis:6379/0} + NOTIFY_SMTP_ENABLED: ${NOTIFY_SMTP_ENABLED:-true} + NOTIFY_SMTP_HOST: ${NOTIFY_SMTP_HOST:-mailpit} + NOTIFY_SMTP_PORT: ${NOTIFY_SMTP_PORT:-1025} + + volumes: + - "./certs:/opt/fastid/certs" + + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/readiness" ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + pg_data: diff --git a/docker-compose.observability.yml b/docker-compose.observability.yml new file mode 100644 index 0000000..e32b72f --- /dev/null +++ b/docker-compose.observability.yml @@ -0,0 +1,19 @@ +x-logging: &default-logging + driver: loki + options: + loki-url: '${LOKI_URL:-http://localhost:3100/api/prom/push}' + loki-pipeline-stages: | + - multiline: + firstline: '^\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2}' + max_wait_time: 3s + - regex: + expression: '^(?P