Сделал деплой
This commit is contained in:
21
.env.prod
21
.env.prod
@@ -1,25 +1,28 @@
|
|||||||
# Django Settings
|
|
||||||
DEBUG=False
|
DEBUG=False
|
||||||
ENVIRONMENT=production
|
ENVIRONMENT=production
|
||||||
|
DJANGO_ENVIRONMENT=production
|
||||||
DJANGO_SETTINGS_MODULE=dbapp.settings.production
|
DJANGO_SETTINGS_MODULE=dbapp.settings.production
|
||||||
SECRET_KEY=change-this-to-a-very-long-random-secret-key-in-production
|
SECRET_KEY=django-insecure-dev-key-only-for-production
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
DB_ENGINE=django.contrib.gis.db.backends.postgis
|
DB_ENGINE=django.contrib.gis.db.backends.postgis
|
||||||
DB_NAME=geodb
|
DB_NAME=geodb
|
||||||
DB_USER=geralt
|
DB_USER=geralt
|
||||||
DB_PASSWORD=CHANGE_THIS_STRONG_PASSWORD
|
DB_PASSWORD=123456
|
||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
|
||||||
# Allowed Hosts (comma-separated)
|
# Allowed Hosts
|
||||||
ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com
|
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
|
||||||
|
|
||||||
|
# CSRF Trusted Origins (include port if using non-standard port)
|
||||||
|
CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1,http://localhost:8080,http://127.0.0.1:8080
|
||||||
|
|
||||||
# PostgreSQL Configuration
|
# PostgreSQL Configuration
|
||||||
POSTGRES_DB=geodb
|
POSTGRES_DB=geodb
|
||||||
POSTGRES_USER=geralt
|
POSTGRES_USER=geralt
|
||||||
POSTGRES_PASSWORD=CHANGE_THIS_STRONG_PASSWORD
|
POSTGRES_PASSWORD=123456
|
||||||
|
|
||||||
# Gunicorn Configuration
|
# Redis Configuration
|
||||||
GUNICORN_WORKERS=3
|
REDIS_URL=redis://redis:6379/1
|
||||||
GUNICORN_TIMEOUT=120
|
CELERY_BROKER_URL=redis://redis:6379/0
|
||||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Ensure shell scripts always use LF line endings
|
||||||
|
*.sh text eol=lf
|
||||||
|
entrypoint.sh text eol=lf
|
||||||
|
|
||||||
|
# Python files
|
||||||
|
*.py text eol=lf
|
||||||
|
|
||||||
|
# Docker files
|
||||||
|
Dockerfile text eol=lf
|
||||||
|
docker-compose*.yaml text eol=lf
|
||||||
|
.dockerignore text eol=lf
|
||||||
@@ -1,57 +1,53 @@
|
|||||||
FROM python:3.13-slim
|
FROM python:3.13.7-slim AS builder
|
||||||
|
|
||||||
# Install system dependencies
|
# Устанавливаем системные библиотеки для GIS, Postgres, сборки пакетов
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
gdal-bin \
|
|
||||||
libgdal-dev \
|
|
||||||
proj-bin \
|
|
||||||
proj-data \
|
|
||||||
libproj-dev \
|
|
||||||
libproj25 \
|
|
||||||
libgeos-dev \
|
|
||||||
libgeos-c1v5 \
|
|
||||||
build-essential \
|
build-essential \
|
||||||
postgresql-client \
|
gdal-bin libgdal-dev \
|
||||||
|
libproj-dev proj-bin \
|
||||||
libpq-dev \
|
libpq-dev \
|
||||||
libpq5 \
|
|
||||||
netcat-openbsd \
|
|
||||||
gcc \
|
|
||||||
g++ \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Set environment variables
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
PYTHONUNBUFFERED=1
|
|
||||||
|
|
||||||
# Set work directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Upgrade pip
|
# Устанавливаем uv пакетно-менеджер глобально
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --no-cache-dir uv
|
||||||
|
|
||||||
# Copy requirements file
|
# Копируем зависимости
|
||||||
COPY requirements.txt ./
|
COPY pyproject.toml uv.lock ./
|
||||||
|
|
||||||
# Install dependencies
|
# Синхронизируем зависимости (включая prod + dev), чтобы билдить
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN uv sync --locked
|
||||||
|
|
||||||
# Copy project files
|
# Копируем весь код приложения
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Create directories
|
# --- рантайм-стадия — минимальный образ для продакшена ---
|
||||||
RUN mkdir -p /app/staticfiles /app/logs /app/media
|
FROM python:3.13.7-slim
|
||||||
|
|
||||||
# Set permissions for entrypoint
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Устанавливаем только runtime-системные библиотеки
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gdal-bin \
|
||||||
|
libproj-dev proj-bin \
|
||||||
|
libpq5 \
|
||||||
|
postgresql-client \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Копируем всё из билдера
|
||||||
|
COPY --from=builder /usr/local/lib/python3.13 /usr/local/lib/python3.13
|
||||||
|
COPY --from=builder /usr/local/bin /usr/local/bin
|
||||||
|
COPY --from=builder /app /app
|
||||||
|
|
||||||
|
# Загружаем переменные окружения из .env (см. docker-compose)
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
PATH="/usr/local/bin:$PATH"
|
||||||
|
|
||||||
|
# Делаем entrypoint.sh исполняемым
|
||||||
RUN chmod +x /app/entrypoint.sh
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
# Create non-root user
|
|
||||||
RUN useradd --create-home --shell /bin/bash app && \
|
|
||||||
chown -R app:app /app
|
|
||||||
|
|
||||||
USER app
|
|
||||||
|
|
||||||
# Expose port
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# Run entrypoint script
|
# Используем entrypoint для инициализации (миграции, статика)
|
||||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
@@ -19,23 +19,29 @@ DEBUG = False
|
|||||||
# In production, specify allowed hosts explicitly from environment variable
|
# In production, specify allowed hosts explicitly from environment variable
|
||||||
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
|
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
|
||||||
|
|
||||||
|
# CSRF trusted origins (required for forms to work behind proxy)
|
||||||
|
CSRF_TRUSTED_ORIGINS = os.getenv(
|
||||||
|
"CSRF_TRUSTED_ORIGINS",
|
||||||
|
"http://localhost,http://127.0.0.1,http://localhost:8080,http://127.0.0.1:8080"
|
||||||
|
).split(",")
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# SECURITY SETTINGS
|
# SECURITY SETTINGS
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# SSL/HTTPS settings
|
# SSL/HTTPS settings (disable for local testing without SSL)
|
||||||
SECURE_SSL_REDIRECT = True
|
SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT", "False") == "True"
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = os.getenv("SESSION_COOKIE_SECURE", "False") == "True"
|
||||||
CSRF_COOKIE_SECURE = True
|
CSRF_COOKIE_SECURE = os.getenv("CSRF_COOKIE_SECURE", "False") == "True"
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
SECURE_BROWSER_XSS_FILTER = True
|
SECURE_BROWSER_XSS_FILTER = True
|
||||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||||
|
|
||||||
# HSTS settings
|
# HSTS settings (disable for local testing)
|
||||||
SECURE_HSTS_SECONDS = 31536000 # 1 year
|
SECURE_HSTS_SECONDS = int(os.getenv("SECURE_HSTS_SECONDS", "0"))
|
||||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
SECURE_HSTS_INCLUDE_SUBDOMAINS = os.getenv("SECURE_HSTS_INCLUDE_SUBDOMAINS", "False") == "True"
|
||||||
SECURE_HSTS_PRELOAD = True
|
SECURE_HSTS_PRELOAD = os.getenv("SECURE_HSTS_PRELOAD", "False") == "True"
|
||||||
|
|
||||||
# Additional security settings
|
# Additional security settings
|
||||||
SECURE_REDIRECT_EXEMPT = []
|
SECURE_REDIRECT_EXEMPT = []
|
||||||
@@ -51,7 +57,7 @@ TEMPLATES = [
|
|||||||
"DIRS": [
|
"DIRS": [
|
||||||
BASE_DIR / "templates",
|
BASE_DIR / "templates",
|
||||||
],
|
],
|
||||||
"APP_DIRS": True,
|
"APP_DIRS": False, # Must be False when using custom loaders
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"context_processors": [
|
"context_processors": [
|
||||||
"django.template.context_processors.debug",
|
"django.template.context_processors.debug",
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from mainapp.views import custom_logout
|
from mainapp.views import custom_logout
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from debug_toolbar.toolbar import debug_toolbar_urls
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls, name='admin'),
|
path('admin/', admin.site.urls, name='admin'),
|
||||||
@@ -28,4 +28,9 @@ urlpatterns = [
|
|||||||
# Authentication URLs
|
# Authentication URLs
|
||||||
path('login/', auth_views.LoginView.as_view(), name='login'),
|
path('login/', auth_views.LoginView.as_view(), name='login'),
|
||||||
path('logout/', custom_logout, name='logout'),
|
path('logout/', custom_logout, name='logout'),
|
||||||
] + debug_toolbar_urls()
|
]
|
||||||
|
|
||||||
|
# Only include debug toolbar in development
|
||||||
|
if settings.DEBUG:
|
||||||
|
from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
urlpatterns += debug_toolbar_urls()
|
||||||
|
|||||||
14
dbapp/entrypoint.sh
Executable file → Normal file
14
dbapp/entrypoint.sh
Executable file → Normal file
@@ -8,30 +8,30 @@ echo "Starting in $ENVIRONMENT mode..."
|
|||||||
|
|
||||||
# Ждем PostgreSQL
|
# Ждем PostgreSQL
|
||||||
echo "Waiting for PostgreSQL..."
|
echo "Waiting for PostgreSQL..."
|
||||||
while ! nc -z $DB_HOST $DB_PORT; do
|
until PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c '\q' 2>/dev/null; do
|
||||||
sleep 0.1
|
echo "PostgreSQL is unavailable - sleeping"
|
||||||
|
sleep 1
|
||||||
done
|
done
|
||||||
echo "PostgreSQL started"
|
echo "PostgreSQL started"
|
||||||
|
|
||||||
# Выполняем миграции
|
# Выполняем миграции
|
||||||
echo "Running migrations..."
|
echo "Running migrations..."
|
||||||
python manage.py migrate --noinput
|
uv run python manage.py migrate --noinput
|
||||||
|
|
||||||
# Собираем статику (только для production)
|
# Собираем статику (только для production)
|
||||||
if [ "$ENVIRONMENT" = "production" ]; then
|
if [ "$ENVIRONMENT" = "production" ]; then
|
||||||
echo "Collecting static files..."
|
echo "Collecting static files..."
|
||||||
python manage.py collectstatic --noinput
|
uv run python manage.py collectstatic --noinput
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Запускаем сервер в зависимости от окружения
|
# Запускаем сервер в зависимости от окружения
|
||||||
if [ "$ENVIRONMENT" = "development" ]; then
|
if [ "$ENVIRONMENT" = "development" ]; then
|
||||||
echo "Starting Django development server..."
|
echo "Starting Django development server..."
|
||||||
exec python manage.py runserver 0.0.0.0:8000
|
exec uv run python manage.py runserver 0.0.0.0:8000
|
||||||
else
|
else
|
||||||
echo "Starting Gunicorn..."
|
echo "Starting Gunicorn..."
|
||||||
exec gunicorn --bind 0.0.0.0:8000 \
|
exec uv run gunicorn --bind 0.0.0.0:8000 \
|
||||||
--workers ${GUNICORN_WORKERS:-3} \
|
--workers ${GUNICORN_WORKERS:-3} \
|
||||||
--timeout ${GUNICORN_TIMEOUT:-120} \
|
--timeout ${GUNICORN_TIMEOUT:-120} \
|
||||||
--reload \
|
|
||||||
dbapp.wsgi:application
|
dbapp.wsgi:application
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ dependencies = [
|
|||||||
"django-dynamic-raw-id>=4.4",
|
"django-dynamic-raw-id>=4.4",
|
||||||
"django-import-export>=4.3.10",
|
"django-import-export>=4.3.10",
|
||||||
"django-leaflet>=0.32.0",
|
"django-leaflet>=0.32.0",
|
||||||
"django-map-widgets>=0.5.1",
|
|
||||||
"django-more-admin-filters>=1.13",
|
"django-more-admin-filters>=1.13",
|
||||||
"dotenv>=0.9.9",
|
"dotenv>=0.9.9",
|
||||||
"flower>=2.0.1",
|
"flower>=2.0.1",
|
||||||
|
|||||||
11
dbapp/uv.lock
generated
11
dbapp/uv.lock
generated
@@ -366,7 +366,6 @@ dependencies = [
|
|||||||
{ name = "django-dynamic-raw-id" },
|
{ name = "django-dynamic-raw-id" },
|
||||||
{ name = "django-import-export" },
|
{ name = "django-import-export" },
|
||||||
{ name = "django-leaflet" },
|
{ name = "django-leaflet" },
|
||||||
{ name = "django-map-widgets" },
|
|
||||||
{ name = "django-more-admin-filters" },
|
{ name = "django-more-admin-filters" },
|
||||||
{ name = "django-redis" },
|
{ name = "django-redis" },
|
||||||
{ name = "dotenv" },
|
{ name = "dotenv" },
|
||||||
@@ -407,7 +406,6 @@ requires-dist = [
|
|||||||
{ name = "django-dynamic-raw-id", specifier = ">=4.4" },
|
{ name = "django-dynamic-raw-id", specifier = ">=4.4" },
|
||||||
{ name = "django-import-export", specifier = ">=4.3.10" },
|
{ name = "django-import-export", specifier = ">=4.3.10" },
|
||||||
{ name = "django-leaflet", specifier = ">=0.32.0" },
|
{ name = "django-leaflet", specifier = ">=0.32.0" },
|
||||||
{ name = "django-map-widgets", specifier = ">=0.5.1" },
|
|
||||||
{ name = "django-more-admin-filters", specifier = ">=1.13" },
|
{ name = "django-more-admin-filters", specifier = ">=1.13" },
|
||||||
{ name = "django-redis", specifier = ">=5.4.0" },
|
{ name = "django-redis", specifier = ">=5.4.0" },
|
||||||
{ name = "dotenv", specifier = ">=0.9.9" },
|
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||||
@@ -598,15 +596,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ec/d3/bf4a46eff75a5a804fc32588696d2dcd04370008041114009f0f35a3fb42/django_leaflet-0.32.0-py3-none-any.whl", hash = "sha256:a17d8e6cc05dd98e8e543fbf198b81dabbf9f195c222e786d1686aeda91c1aa8", size = 582439, upload-time = "2025-05-14T12:49:34.151Z" },
|
{ url = "https://files.pythonhosted.org/packages/ec/d3/bf4a46eff75a5a804fc32588696d2dcd04370008041114009f0f35a3fb42/django_leaflet-0.32.0-py3-none-any.whl", hash = "sha256:a17d8e6cc05dd98e8e543fbf198b81dabbf9f195c222e786d1686aeda91c1aa8", size = 582439, upload-time = "2025-05-14T12:49:34.151Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "django-map-widgets"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/78/50/651dae7335fc9c6df7b1ab27c49b1cc98245ac0d61750538a192da19e671/django_map_widgets-0.5.1.tar.gz", hash = "sha256:68e81f9c58c1cd6d180421220a4d100a185c8062ae0ca7be790658fcfd4eda1d", size = 160819, upload-time = "2024-07-09T17:37:50.717Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/75/7f1782c9fa3c07c2ca63ce7b65c4838afb568a5ea71aa119aaa9dc456d8b/django_map_widgets-0.5.1-py3-none-any.whl", hash = "sha256:7307935163b46c6a2a225c85c91c7262a8b47a5c3aefbbc6d8fc7a5fda53b7cd", size = 256008, upload-time = "2024-07-09T17:37:48.941Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-more-admin-filters"
|
name = "django-more-admin-filters"
|
||||||
version = "1.13"
|
version = "1.13"
|
||||||
|
|||||||
@@ -1,95 +1,60 @@
|
|||||||
services:
|
services:
|
||||||
db:
|
|
||||||
image: postgis/postgis:17-3.4
|
|
||||||
container_name: postgres-postgis-prod
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-geodb}
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-geralt}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-123456}
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
volumes:
|
|
||||||
- postgres_data_prod:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-geralt} -d ${POSTGRES_DB:-geodb}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
networks:
|
|
||||||
- app-network
|
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build:
|
build:
|
||||||
context: ./dbapp
|
context: ./dbapp
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: django-app-prod
|
env_file:
|
||||||
restart: always
|
- .env.prod
|
||||||
environment:
|
|
||||||
- DEBUG=False
|
|
||||||
- ENVIRONMENT=production
|
|
||||||
- DJANGO_SETTINGS_MODULE=dbapp.settings.production
|
|
||||||
- SECRET_KEY=${SECRET_KEY}
|
|
||||||
- DB_ENGINE=django.contrib.gis.db.backends.postgis
|
|
||||||
- DB_NAME=${DB_NAME:-geodb}
|
|
||||||
- DB_USER=${DB_USER:-geralt}
|
|
||||||
- DB_PASSWORD=${DB_PASSWORD:-123456}
|
|
||||||
- DB_HOST=db
|
|
||||||
- DB_PORT=5432
|
|
||||||
- ALLOWED_HOSTS=${ALLOWED_HOSTS:-localhost,127.0.0.1}
|
|
||||||
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-3}
|
|
||||||
- GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-120}
|
|
||||||
ports:
|
|
||||||
- "8000:8000"
|
|
||||||
volumes:
|
|
||||||
- static_volume_prod:/app/staticfiles
|
|
||||||
- media_volume_prod:/app/media
|
|
||||||
- logs_volume_prod:/app/logs
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
- db
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
|
||||||
- app-network
|
|
||||||
|
|
||||||
tileserver:
|
|
||||||
image: maptiler/tileserver-gl:latest
|
|
||||||
container_name: tileserver-gl-prod
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./tiles:/data
|
- static_volume:/app/staticfiles
|
||||||
- tileserver_config_prod:/config
|
expose:
|
||||||
environment:
|
- 8000
|
||||||
- VERBOSE=false
|
|
||||||
networks:
|
worker:
|
||||||
- app-network
|
build:
|
||||||
|
context: ./dbapp
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
env_file:
|
||||||
|
- .env.prod
|
||||||
|
entrypoint: []
|
||||||
|
command: ["uv", "run", "celery", "-A", "dbapp", "worker", "--loglevel=INFO"]
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
- web
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgis/postgis:18-3.6
|
||||||
|
container_name: postgres-postgis
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env.prod
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql
|
||||||
|
# networks:
|
||||||
|
# - app-network
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
container_name: nginx-prod
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
||||||
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
|
||||||
- static_volume_prod:/app/staticfiles:ro
|
|
||||||
- media_volume_prod:/app/media:ro
|
|
||||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- web
|
- web
|
||||||
networks:
|
ports:
|
||||||
- app-network
|
- 8080:80
|
||||||
|
volumes:
|
||||||
|
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
- static_volume:/usr/share/nginx/html/static
|
||||||
|
# если у тебя медиа — можно замонтировать том media
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data_prod:
|
pgdata:
|
||||||
static_volume_prod:
|
static_volume:
|
||||||
media_volume_prod:
|
|
||||||
logs_volume_prod:
|
|
||||||
tileserver_config_prod:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
app-network:
|
|
||||||
driver: bridge
|
|
||||||
@@ -1,39 +1,39 @@
|
|||||||
events {
|
upstream django {
|
||||||
worker_connections 1024;
|
server web:8000;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
server {
|
||||||
include /etc/nginx/mime.types;
|
listen 80;
|
||||||
default_type application/octet-stream;
|
server_name _;
|
||||||
|
|
||||||
# Log format
|
proxy_connect_timeout 300s;
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
proxy_send_timeout 300s;
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
proxy_read_timeout 300s;
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
send_timeout 300s;
|
||||||
|
# Максимальный размер тела запроса, например для загрузки файлов
|
||||||
|
client_max_body_size 200m;
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log main;
|
# Статические файлы (статика Django)
|
||||||
error_log /var/log/nginx/error.log;
|
location /static/ {
|
||||||
|
alias /usr/share/nginx/html/static/; # ← тут путь в контейнере nginx, куда монтируется том со static
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, max-age=2592000";
|
||||||
|
}
|
||||||
|
|
||||||
# Security headers
|
# Медиа-файлы, если есть MEDIA_URL
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
location /media/ {
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
alias /usr/share/nginx/media/; # путь, куда монтируется media-том
|
||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
expires 30d;
|
||||||
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
add_header Cache-Control "public, max-age=2592000";
|
||||||
|
}
|
||||||
|
|
||||||
# Proxy settings
|
# Прокси для всех остальных запросов на Django (асинхронный / uvicorn или gunicorn)
|
||||||
proxy_set_header Host $http_host;
|
location / {
|
||||||
|
proxy_pass http://django;
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header X-Forwarded-Host $server_name;
|
proxy_redirect off;
|
||||||
|
}
|
||||||
# Gzip compression
|
|
||||||
gzip on;
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_min_length 1024;
|
|
||||||
# gzip_proxied expired no-cache no-store private must-revalidate auth;
|
|
||||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
|
||||||
|
|
||||||
# Include server blocks
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user