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_config = AuthXConfig(
|
||||||
JWT_ALGORITHM=app.config.jwt_algorithm,
|
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,
|
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"
|
database_driver = os.getenv("DATABASE_DRIVER")
|
||||||
smtp_port = 2525
|
database_host = os.getenv("DATABASE_HOST")
|
||||||
smtp_username = "university@nspotapov.ru"
|
database_port = int(os.getenv("DATABASE_PORT"))
|
||||||
smtp_from = "university@nspotapov.ru"
|
database_name = os.getenv("DATABASE_NAME")
|
||||||
smtp_password = "V%P2WUe2wnwL"
|
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"
|
smtp_host = os.getenv("SMTP_HOST")
|
||||||
redis_port = 6379
|
smtp_port = os.getenv("SMTP_PORT")
|
||||||
redis_username = None
|
smtp_username = os.getenv("SMTP_USERNAME")
|
||||||
redis_password = None
|
smtp_from = os.getenv("SMTP_FROM")
|
||||||
redis_db = 0
|
smtp_password = os.getenv("SMTP_PASSWORD")
|
||||||
|
|
||||||
jwt_algorithm = "HS256"
|
otp_code_expired_time = int(os.getenv("OTP_CODE_EXPIRED_TIME", "5")) # minutes
|
||||||
swt_secret_key = "SECRET_KEY"
|
|
||||||
|
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"]
|
jwt_token_location = ["headers", "cookies", "query"]
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import dotenv
|
||||||
from fastapi import FastAPI, APIRouter
|
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
|
from app.common.security import jwt_security
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ class SQLAlchemyRepository(AbstractRepository):
|
|||||||
|
|
||||||
async def add_one(self, data: dict) -> int:
|
async def add_one(self, data: dict) -> int:
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
stmt = insert(self.model).values(**data).returning(self.model.id)
|
stmt = insert(self.model).values(**data)
|
||||||
res = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return res.scalar_one()
|
return result.lastrowid
|
||||||
|
|
||||||
async def edit_one(self, id: int, data: dict) -> int:
|
async def edit_one(self, id: int, data: dict) -> int:
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
stmt = update(self.model).values(**data).filter_by(id=id).returning(self.model.id)
|
stmt = update(self.model).values(**data).filter_by(id=id)
|
||||||
res = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return res.scalar_one()
|
return result.lastrowid
|
||||||
|
|
||||||
async def get_all(self) -> List[BaseReadSchema]:
|
async def get_all(self) -> List[BaseReadSchema]:
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
@@ -40,10 +40,10 @@ class SQLAlchemyRepository(AbstractRepository):
|
|||||||
|
|
||||||
async def delete_one(self, id: int) -> int:
|
async def delete_one(self, id: int) -> int:
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
stmt = delete(self.model).filter_by(id=id).returning(self.model.id)
|
stmt = delete(self.model).filter_by(id=id)
|
||||||
res = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return res.scalar_one()
|
return result.lastrowid
|
||||||
|
|
||||||
async def find_all(self, **filter_by) -> list[BaseReadSchema]:
|
async def find_all(self, **filter_by) -> list[BaseReadSchema]:
|
||||||
async with async_session_maker() as session:
|
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