Docker Compose deploy ready
This commit is contained in:
23
backend/.env.example
Normal file
23
backend/.env.example
Normal file
@@ -0,0 +1,23 @@
|
||||
DATABASE_DRIVER=mysql+aiomysql
|
||||
DATABASE_HOST=mysql
|
||||
DATABASE_PORT=3306
|
||||
DATABASE_NAME=app
|
||||
DATABASE_USERNAME=root
|
||||
DATABASE_PASSWORD=supersecret
|
||||
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=2525
|
||||
SMTP_USERNAME=email@example.com
|
||||
SMTP_FROM=email@example.com
|
||||
SMTP_PASSWORD=password
|
||||
|
||||
# OTP_CODE_EXPIRED_TIME = 5
|
||||
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
# REDIS_USERNAME=
|
||||
# REDIS_PASSWORD=
|
||||
# REDIS_DB=0
|
||||
|
||||
# JWT_ALGORITHM=HS256
|
||||
JWT_SECRET_KEY=SECRET_KEY
|
||||
@@ -0,0 +1,53 @@
|
||||
# Базовый образ для сборки
|
||||
FROM python:3.12.3-slim-bookworm AS builder
|
||||
|
||||
# Установка системных зависимостей для сборки
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Создание виртуального окружения
|
||||
RUN python -m venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# Копирование и установка зависимостей
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Финальный образ
|
||||
FROM python:3.12.3-slim-bookworm
|
||||
|
||||
# Установка только необходимых системных пакетов
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Копирование виртуального окружения из builder
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# Создание пользователя для безопасности
|
||||
RUN groupadd -r fastapi && useradd -r -g fastapi fastapi
|
||||
|
||||
# Создание рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование приложения
|
||||
COPY --chown=fastapi:fastapi . .
|
||||
|
||||
# Переключение на непривилегированного пользователя
|
||||
USER fastapi
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
|
||||
# Экспорт порта
|
||||
EXPOSE 8000
|
||||
|
||||
# Переменные окружения
|
||||
ENV PYTHONPATH=/app \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Команда запуска
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
@@ -11,7 +11,7 @@ def get_password_hash(password: str) -> str:
|
||||
|
||||
jwt_config = AuthXConfig(
|
||||
JWT_ALGORITHM=app.config.jwt_algorithm,
|
||||
JWT_SECRET_KEY=app.config.swt_secret_key,
|
||||
JWT_SECRET_KEY=app.config.jwt_secret_key,
|
||||
JWT_TOKEN_LOCATION=app.config.jwt_token_location,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,19 +1,34 @@
|
||||
database_url = "sqlite+aiosqlite:///data/database.sqlite"
|
||||
import os
|
||||
|
||||
smtp_host = "smtp.beget.com"
|
||||
smtp_port = 2525
|
||||
smtp_username = "university@nspotapov.ru"
|
||||
smtp_from = "university@nspotapov.ru"
|
||||
smtp_password = "V%P2WUe2wnwL"
|
||||
database_driver = os.getenv("DATABASE_DRIVER")
|
||||
database_host = os.getenv("DATABASE_HOST")
|
||||
database_port = int(os.getenv("DATABASE_PORT"))
|
||||
database_name = os.getenv("DATABASE_NAME")
|
||||
database_username = os.getenv("DATABASE_USERNAME")
|
||||
database_password = os.getenv("DATABASE_PASSWORD")
|
||||
|
||||
otp_code_expired_time = 5 # minutes
|
||||
database_url = ''.join(
|
||||
[
|
||||
database_driver, "://",
|
||||
database_username, ":", database_password, ("@" if database_username or database_password else ""),
|
||||
database_host, (":" if str(database_port) else ""), str(database_port), "/", database_name
|
||||
]
|
||||
)
|
||||
|
||||
redis_host = "127.0.0.1"
|
||||
redis_port = 6379
|
||||
redis_username = None
|
||||
redis_password = None
|
||||
redis_db = 0
|
||||
smtp_host = os.getenv("SMTP_HOST")
|
||||
smtp_port = os.getenv("SMTP_PORT")
|
||||
smtp_username = os.getenv("SMTP_USERNAME")
|
||||
smtp_from = os.getenv("SMTP_FROM")
|
||||
smtp_password = os.getenv("SMTP_PASSWORD")
|
||||
|
||||
jwt_algorithm = "HS256"
|
||||
swt_secret_key = "SECRET_KEY"
|
||||
otp_code_expired_time = int(os.getenv("OTP_CODE_EXPIRED_TIME", "5")) # minutes
|
||||
|
||||
redis_host = os.getenv("REDIS_HOST")
|
||||
redis_port = int(os.getenv("REDIS_PORT"))
|
||||
redis_username = os.getenv("REDIS_USERNAME")
|
||||
redis_password = os.getenv("REDIS_PASSWORD")
|
||||
redis_db = int(os.getenv("REDIS_DB", "0"))
|
||||
|
||||
jwt_algorithm = os.getenv("JWT_ALGORITHM", "HS256")
|
||||
jwt_secret_key = os.getenv("JWT_SECRET_KEY")
|
||||
jwt_token_location = ["headers", "cookies", "query"]
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import os
|
||||
|
||||
import dotenv
|
||||
from fastapi import FastAPI, APIRouter
|
||||
|
||||
BASE_DIR: str = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
if os.path.exists(os.path.join(BASE_DIR, ".env")):
|
||||
dotenv.load_dotenv(os.path.join(BASE_DIR, ".env"))
|
||||
|
||||
from app.common.security import jwt_security
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@@ -12,17 +12,17 @@ class SQLAlchemyRepository(AbstractRepository):
|
||||
|
||||
async def add_one(self, data: dict) -> int:
|
||||
async with async_session_maker() as session:
|
||||
stmt = insert(self.model).values(**data).returning(self.model.id)
|
||||
res = await session.execute(stmt)
|
||||
stmt = insert(self.model).values(**data)
|
||||
result = await session.execute(stmt)
|
||||
await session.commit()
|
||||
return res.scalar_one()
|
||||
return result.lastrowid
|
||||
|
||||
async def edit_one(self, id: int, data: dict) -> int:
|
||||
async with async_session_maker() as session:
|
||||
stmt = update(self.model).values(**data).filter_by(id=id).returning(self.model.id)
|
||||
res = await session.execute(stmt)
|
||||
stmt = update(self.model).values(**data).filter_by(id=id)
|
||||
result = await session.execute(stmt)
|
||||
await session.commit()
|
||||
return res.scalar_one()
|
||||
return result.lastrowid
|
||||
|
||||
async def get_all(self) -> List[BaseReadSchema]:
|
||||
async with async_session_maker() as session:
|
||||
@@ -40,10 +40,10 @@ class SQLAlchemyRepository(AbstractRepository):
|
||||
|
||||
async def delete_one(self, id: int) -> int:
|
||||
async with async_session_maker() as session:
|
||||
stmt = delete(self.model).filter_by(id=id).returning(self.model.id)
|
||||
res = await session.execute(stmt)
|
||||
stmt = delete(self.model).filter_by(id=id)
|
||||
result = await session.execute(stmt)
|
||||
await session.commit()
|
||||
return res.scalar_one()
|
||||
return result.lastrowid
|
||||
|
||||
async def find_all(self, **filter_by) -> list[BaseReadSchema]:
|
||||
async with async_session_maker() as session:
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports: [ 127.0.0.1:8000:8000 ]
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
|
||||
mysql:
|
||||
image: mysql:8.4.6
|
||||
ports: [ 127.0.0.1:3306:3306 ]
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-supersecret}
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin", "-u", "root", "--password=${MYSQL_ROOT_PASSWORD:-supersecret}", "ping", "-h", "localhost" ]
|
||||
timeout: 5s
|
||||
start_period: 5s
|
||||
interval: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:8.2.1
|
||||
ports: [ 127.0.0.1:6379:6379 ]
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
7
backend/entrypoint.sh
Normal file
7
backend/entrypoint.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
alembic upgrade head
|
||||
|
||||
exec "$@"
|
||||
Binary file not shown.
Reference in New Issue
Block a user