diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f1d630 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +__pycache__ +**/__pycache__ +**/__pycache__/* +venv +*.pyc +*.pyo +*.pyd +*.sqlite3 +.Python +env +pip-log.txt +pip-delete-this-directory.txt +.tox +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.log +.git +.vscode +.prettierrc +.mypy_cache +.pytest_cache +.hypothesis +Dockerfile +.env +.dockerignore +docker-compose.override.yml +docker-compose.override +docker-compose.yml +nginx +media +static +postgres \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d3a85fe..9cd3eaf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,76 @@ -# Базовый образ -FROM python:3.11 +FROM python:3.13-slim AS builder + +RUN mkdir /www + +WORKDIR /www -# Устанавливаем рабочую директорию внутри контейнера -WORKDIR /app +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 -# Копируем зависимости -COPY requirements.txt . - -# Устанавливаем зависимости +# RUN apt-get update && apt-get install -y --no-install-recommends \ +# libpq-dev \ +# gcc \ +# g++ \ +# libffi-dev \ +# libssl-dev \ +# libxml2-dev \ +# libxslt1-dev \ +# zlib1g-dev \ +# libjpeg-dev \ +# libfreetype6-dev && \ +# rm -rf /var/lib/apt/lists/* && \ +# apt-get clean && \ +# apt-get autoclean + +RUN pip install --upgrade pip + +COPY requirements.txt /www/ RUN pip install --no-cache-dir -r requirements.txt -# Копируем все файлы проекта -COPY . . +COPY requirements-prod.txt /www/ +RUN pip install --no-cache-dir -r requirements-prod.txt + +# Stage 2: Production stage +FROM python:3.13-slim -# Открываем порт (опционально, если хочешь) -EXPOSE 8000 +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpq-dev \ + libssl-dev \ + gettext && \ + apt-get clean && \ + apt-get autoclean + +RUN useradd -m -r wwwuser && \ + mkdir /www && \ + chown -R wwwuser /www + +# Copy the Python dependencies from the builder stage +COPY --from=builder /usr/local/lib/python3.13/site-packages/ /usr/local/lib/python3.13/site-packages/ +COPY --from=builder /usr/local/bin/ /usr/local/bin/ + +# Set the working directory +WORKDIR /www + +# Copy application code +COPY --chown=wwwuser:wwwuser . . -# Запускаем сервер -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] +RUN chmod +x /www/entrypoint.sh && \ + chmod +x /www/manage.py + +RUN mkdir -p /static && \ + mkdir -p /media && \ + chown -R wwwuser:wwwuser /static && \ + chown -R wwwuser:wwwuser /media + +# Set environment variables to optimize Python +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Switch to non-root user +USER wwwuser + +# Expose the application port +EXPOSE 8000 + +# Run Django’s development server +CMD ["/www/entrypoint.sh"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 8c6b5bd..8763c10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: '3.8' + services: db: image: postgres:14 @@ -7,24 +9,40 @@ services: POSTGRES_PASSWORD: mypass volumes: - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" + networks: + - app_network web: build: . - command: python manage.py runserver 0.0.0.0:8000 - volumes: - - .:/app ports: - "8000:8000" + volumes: + - ./static:/static:rw + - ./media:/media:rw + env_file: .env depends_on: - db - environment: - - DB_NAME=mydb - - DB_USER=myuser - - DB_PASSWORD=mypass - - DB_HOST=db - - DB_PORT=5432 + networks: + - app_network + + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./static:/var/www/static + - ./media:/var/www/media + depends_on: + - web + networks: + - app_network volumes: postgres_data: + static_volume: + media_volume: + +networks: + app_network: + driver: bridge \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..cb046a1 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +python manage.py collectstatic --noinput +python manage.py migrate --noinput +python -m daphne testapi.asgi:application -b 0.0.0.0 -p 8000 diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..0454044 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,34 @@ +upstream django { + server web:8000; +} + +server { + listen 80; + server_name localhost; + + # Static files + location /static/ { + alias /app/static/; + expires 1y; + access_log off; + add_header Cache-Control "public"; + gzip_static on; + } + + # Media files + location /media/ { + alias /app/media/; + expires 7d; + access_log off; + add_header Cache-Control "public"; + } + + # Django app + location / { + proxy_pass http://django; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/requirements-prod.txt b/requirements-prod.txt new file mode 100644 index 0000000..2679c52 --- /dev/null +++ b/requirements-prod.txt @@ -0,0 +1 @@ +daphne \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 72cda28..688620c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,4 @@ drf-spectacular requests django-cors-headers Pillow - +python-dotenv diff --git a/testapi/settings.py b/testapi/settings.py index 8d50619..43bf2ca 100644 --- a/testapi/settings.py +++ b/testapi/settings.py @@ -12,6 +12,9 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ from pathlib import Path import os +from dotenv import load_dotenv +load_dotenv() + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -19,16 +22,28 @@ BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ +# Environment flag +PRODUCTION = os.getenv('DJANGO_ENV') == 'production' + # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-np(nxnh6mw)v4pa2n2z3pl_5&!2z$jshhak9r3v=y1u9rd*sl!' +SECRET_KEY = os.getenv( + 'SECRET_KEY', 'django-insecure-np(nxnh6mw)v4pa2n2z3pl_5&!2z$jshhak9r3v=y1u9rd*sl!') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = os.getenv('DEBUG', 'False') == 'True' -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost').split(',') + +# Static files (CSS, JavaScript, Images) +STATIC_URL = os.getenv('STATIC_URL', '/static/') +STATIC_ROOT = os.getenv('STATIC_ROOT', os.path.join(BASE_DIR, 'static')) + +# Media files (user uploaded) +MEDIA_URL = os.getenv('MEDIA_URL', '/media/') +MEDIA_ROOT = os.getenv('MEDIA_ROOT', os.path.join(BASE_DIR, 'media')) # Куда сохранять загруженные файлы -# Application definitionЫ +# Application definition INSTALLED_APPS = [ 'django.contrib.admin', @@ -89,22 +104,22 @@ WSGI_APPLICATION = 'testapi.wsgi.application' # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases -if DEBUG: +if PRODUCTION: DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('DB_NAME'), + 'USER': os.getenv('DB_USER'), + 'PASSWORD': os.getenv('DB_PASSWORD'), + 'HOST': os.getenv('DB_HOST'), + 'PORT': os.getenv('DB_PORT'), } } else: DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'drfapi', # Your database name - 'USER': 'postgres', # Your PostgreSQL username - 'PASSWORD': '1235', # Your PostgreSQL password - 'HOST': 'localhost', # Or your DB server's IP - 'PORT': '5432', # Default PostgreSQL port + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', } } @@ -176,4 +191,6 @@ CSRF_COOKIE_SAMESITE = 'None' # temp CSRF_COOKIE_SECURE = False SESSION_COOKIE_SAMESITE = 'None' # temp SESSION_COOKIE_SECURE = False -CSRF_TRUSTED_ORIGINS = ['http://localhost:5173', 'http://127.0.0.1:5173'] \ No newline at end of file +CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', '').split(',') + +