diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..916a7ca --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Ignore files generated by text editors +*~ +*.swp + +# Ignore version control files and directories +.git +.gitignore +svn +CVS + +# Ignore Python bytecode files and cache directories +*.pyc +__pycache__ + +# Ignore local development settings files +.env +.local +.venv + +# Ignore Docker build context directories +.dockerignore +.docker + +# Ignore node_modules directory +node_modules + +# Ignore Celery beat schedule file +celerybeat-schedule + +# Ignore Celery worker pid file +celeryd.pid + +# Ignore Celery task result files +celery-task-meta-* + +# Ignore Django static files +/static + +# Ignore Django media files +/media diff --git a/.gitignore b/.gitignore index f42bd8b..93baa16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Application Directories src/staticfiles/ src/mediafiles/ +src/static/ src/media .venv @@ -65,7 +66,6 @@ celerybeat.pid *.sage.py .env .env.prod -.venv env/ venv/ ENV/ @@ -80,4 +80,5 @@ venv.bak/ dmypy.json .pyre/ .pytype/ -cython_debug/ \ No newline at end of file +cython_debug/ +letsencrypt/ \ No newline at end of file diff --git a/deployment/scripts/backend/start.sh b/deployment/scripts/backend/start.sh index aadfcff..74df41f 100644 --- a/deployment/scripts/backend/start.sh +++ b/deployment/scripts/backend/start.sh @@ -10,4 +10,4 @@ else python manage.py collectstatic --noinput fi -gunicorn "$APP_NAME".wsgi:application --bind "$APP_HOST":"$APP_PORT" --workers 3 --timeout 60 --graceful-timeout 60 --log-level=info +gunicorn "$APP_NAME".wsgi:application --bind "$APP_HOST":"$APP_PORT" --workers 3 --log-level=debug diff --git a/deployment/scripts/nginx/nginx.conf b/deployment/scripts/nginx/nginx.conf new file mode 100644 index 0000000..097e01e --- /dev/null +++ b/deployment/scripts/nginx/nginx.conf @@ -0,0 +1,13 @@ +events {} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 80; + location /static/ { + alias /usr/src/app/static/; + } + } +} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 96ea7a3..4b4f44f 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -13,17 +13,17 @@ services: volumes: - ./src:/usr/src/app/ - ./deployment/scripts:/app/deployment/scripts/ + - static_files:/usr/src/app/static labels: - "traefik.enable=true" - - "traefik.http.routers.${APP_NAME}-backend.rule=Host(`${APP_DOMAIN}`)" + - "traefik.http.routers.${APP_NAME}-backend.rule=Host(`${APP_HOST}`)" - "traefik.http.routers.${APP_NAME}-backend.entrypoints=web-secure" - "traefik.http.services.${APP_NAME}-backend.loadbalancer.server.port=${APP_PORT}" -# ports: -# - "${APP_PORT}:${APP_PORT}" + - "traefik.http.routers.${APP_NAME}-backend.tls.certresolver=letsencrypt" env_file: .env depends_on: - db: - condition: service_healthy + - db + - redis command: [ "/bin/sh", "/app/deployment/scripts/backend/start.sh" ] db: @@ -36,8 +36,7 @@ services: ports: - "5432:5432" shm_size: 1g - healthcheck: - test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}", "-d", "${POSTGRES_DB}"] + redis: container_name: "${APP_NAME}-redis" @@ -65,21 +64,43 @@ services: container_name: "${APP_NAME}-celery-beat" command: [ "/bin/sh", "/app/deployment/scripts/celery/start-beat.sh" ] + nginx: + image: nginx:latest + container_name: "${APP_NAME}-nginx" + volumes: + - ./deployment/scripts/nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - static_files:/usr/src/app/static + labels: + - "traefik.enable=true" + - "traefik.http.routers.${APP_NAME}-nginx.rule=Host(`${APP_HOST}`) && PathPrefix(`/static`)" + - "traefik.http.routers.${APP_NAME}-nginx.entrypoints=web" + - "traefik.http.services.${APP_NAME}-nginx.loadbalancer.server.port=80" + depends_on: + - backend + traefik: image: traefik:v2.5 container_name: "${APP_NAME}-traefik" command: - - "--api.insecure=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" + - "--entrypoints.web-secure.address=:443" + - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" ports: - "80:80" + - "443:443" volumes: - "/var/run/docker.sock:/var/run/docker.sock" + - "./letsencrypt:/letsencrypt" networks: - default volumes: + static_files: postgres_data_dir: - redis_data: \ No newline at end of file + redis_data: + letsencrypt: diff --git a/docker-compose.yml b/docker-compose.yml index da2c59d..a6a4952 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,16 +13,16 @@ services: volumes: - ./src:/usr/src/app/ - ./deployment/scripts:/app/deployment/scripts/ + - static_files:/usr/src/app/static labels: - "traefik.enable=true" - "traefik.http.routers.${APP_NAME}-backend.rule=Host(`${APP_HOST}`)" - - "traefik.http.routers.${APP_NAME}-backend.entrypoints=web-secure" + - "traefik.http.routers.${APP_NAME}-backend.entrypoints=web" - "traefik.http.services.${APP_NAME}-backend.loadbalancer.server.port=${APP_PORT}" - - "traefik.http.routers.${APP_NAME}-backend.tls.certresolver=letsencrypt" env_file: .env -# depends_on: -# db: -# condition: service_healthy + depends_on: + - db + - redis command: [ "/bin/sh", "/app/deployment/scripts/backend/start.sh" ] db: @@ -35,8 +35,7 @@ services: ports: - "5432:5432" shm_size: 1g -# healthcheck: -# test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}", "-d", "${POSTGRES_DB}"] + redis: container_name: "${APP_NAME}-redis" @@ -64,30 +63,41 @@ services: container_name: "${APP_NAME}-celery-beat" command: [ "/bin/sh", "/app/deployment/scripts/celery/start-beat.sh" ] + nginx: + image: nginx:latest + container_name: "${APP_NAME}-nginx" + volumes: + - ./deployment/scripts/nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - static_files:/usr/src/app/static + labels: + - "traefik.enable=true" + - "traefik.http.routers.${APP_NAME}-nginx.rule=Host(`${APP_HOST}`) && PathPrefix(`/static`)" + - "traefik.http.routers.${APP_NAME}-nginx.entrypoints=web" + - "traefik.http.services.${APP_NAME}-nginx.loadbalancer.server.port=80" + depends_on: + - backend + traefik: image: traefik:v2.5 container_name: "${APP_NAME}-traefik" - env_file: - - .env command: + - "--api.insecure=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - - "--entrypoints.web-secure.address=:443" - - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}" - - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" - - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" ports: - "80:80" - - "443:443" + - "8080:8080" volumes: - "/var/run/docker.sock:/var/run/docker.sock" - - "./letsencrypt:/letsencrypt" networks: - default + depends_on: + - db + - backend + - redis volumes: postgres_data_dir: redis_data: - letsencrypt: \ No newline at end of file + static_files: diff --git a/env.example b/env.example index c681fa7..308da1d 100644 --- a/env.example +++ b/env.example @@ -2,8 +2,9 @@ APP_NAME=project_name APP_HOST=0.0.0.0 APP_PORT=8000 -APP_SECRET_KEY='ur secret key' +SECRET_KEY='ur secret key' APP_ENV=dev +DEBUG=True DJANGO_SETTINGS_MODULE=project_name.settings.dev ALLOWED_HOSTS='app.backend.dev' CSRF_TRUSTED_ORIGINS='http://app.backend.dev' diff --git a/src/project_name/settings/base.py b/src/project_name/settings/base.py index 3ac33f9..df15497 100644 --- a/src/project_name/settings/base.py +++ b/src/project_name/settings/base.py @@ -1,13 +1,12 @@ -from os import getenv as os_getenv, path as os_path # noqa +from os import getenv as os_getenv, path as os_path # noqa from pathlib import Path from django.core.management.utils import get_random_secret_key # Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent +BASE_DIR = Path(__file__).resolve().parent.parent.parent - -SECRET_KEY = os_getenv("APP_SECRET_KEY", get_random_secret_key()) # If SECRET_KEY is not set, generate a random one +SECRET_KEY = os_getenv("SECRET_KEY", get_random_secret_key()) # If SECRET_KEY is not set, generate a random one APP_ENV = os_getenv("APP_ENV", "dev") DEBUG = os_getenv("DEBUG", "true").lower() in ["True", "true", "1", "yes", "y"] @@ -35,6 +34,7 @@ INSTALLED_APPS = [ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', + "whitenoise.middleware.WhiteNoiseMiddleware", 'django.contrib.sessions.middleware.SessionMiddleware', "corsheaders.middleware.CorsMiddleware", # CorsMiddleware should be placed as high as possible, 'django.middleware.common.CommonMiddleware', @@ -64,7 +64,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'project_name.wsgi.application' - # Database # https://docs.djangoproject.com/en/4.2/ref/settings/#databases @@ -79,7 +78,6 @@ DATABASES = { } } - # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators @@ -98,7 +96,6 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] - # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ @@ -110,19 +107,19 @@ USE_I18N = True USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_URL = 'static/' +STATIC_ROOT = os_path.join(BASE_DIR, 'static') MEDIA_URL = 'media/' +MEDIA_ROOT = os_path.join(BASE_DIR, 'media') # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - # Redis REDIS_DB_KEYS = { "dev": 0, diff --git a/src/project_name/settings/prod.py b/src/project_name/settings/prod.py index 79c5393..a31da0a 100644 --- a/src/project_name/settings/prod.py +++ b/src/project_name/settings/prod.py @@ -1 +1,7 @@ -from .base import * # noqa \ No newline at end of file +from .base import * # noqa + +STORAGES = { + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} diff --git a/src/requirements.txt b/src/requirements.txt index c2a652d..d626fb3 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -19,3 +19,4 @@ typing_extensions==4.5.0 vine==5.0.0 wcwidth==0.2.6 redis==4.5.5 +whitenoise==6.4.0