diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..10720bbe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +__pycache__ +*.pyc +venv +.env +db.sqlite3 +static +media diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ad85b3e4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +FROM python:3.12-slim +LABEL maintainer="your_email@example.com" + +ENV PYTHONUNBUFFERED 1 +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpq-dev \ + bash \ + gosu \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libc6-dev \ + && pip install --no-cache-dir -r requirements.txt \ + && apt-get purge -y --auto-remove gcc libc6-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN adduser --disabled-password --no-create-home --shell /bin/bash django-user && \ + mkdir -p /vol/web/media /vol/web/static && \ + chown -R django-user:django-user /vol && \ + chmod -R 755 /vol + +COPY . . +RUN chown -R django-user:django-user /app + +COPY ./scripts/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/cinema/management/__init__.py b/cinema/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cinema/management/commands/__init__.py b/cinema/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cinema/management/commands/wait_for_db.py b/cinema/management/commands/wait_for_db.py new file mode 100644 index 00000000..56bc6e35 --- /dev/null +++ b/cinema/management/commands/wait_for_db.py @@ -0,0 +1,29 @@ +import time +from django.db import connections +from django.db.utils import OperationalError +from django.core.management.base import BaseCommand, CommandError + + +class Command(BaseCommand): + def handle(self, *args, **options): + self.stdout.write("Waiting for database...") + attempts = 0 + max_attempts = 20 + + while attempts < max_attempts: + try: + db_conn = connections["default"] + db_conn.cursor() + self.stdout.write(self.style.SUCCESS( + f"Database available after {attempts + 1} attempt(s)!" + )) + return + except OperationalError: + attempts += 1 + self.stdout.write( + f"Database unavailable " + f"(attempt {attempts}/{max_attempts}), waiting 1s..." + ) + time.sleep(1) + + raise CommandError("Database unavailable after maximum attempts.") diff --git a/cinema_service/settings.py b/cinema_service/settings.py index 90dde772..94574abc 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -1,7 +1,7 @@ """ Django settings for cinema_service project. -Generated by 'django-admin startproject' using Django 4.0.4. +Generated by "django-admin startproject" using Django 4.0.4. For more information on this file, see https://docs.djangoproject.com/en/4.0/topics/settings/ @@ -9,10 +9,11 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.0/ref/settings/ """ +import os from datetime import timedelta from pathlib import Path -# Build paths inside the project like this: BASE_DIR / 'subdir'. +# Build paths inside the project like this: BASE_DIR / "subdir". BASE_DIR = Path(__file__).resolve().parent.parent @@ -24,10 +25,10 @@ "django-insecure-6vubhk2$++agnctay_4pxy_8cq)mosmn(*-#2b^v4cgsh-^!i3" ) -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +# SECURITY WARNING: don"t run with debug turned on in production! +DEBUG = os.getenv("DEBUG", "False") == "True" -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "127.0.0.1,localhost").split(",") INTERNAL_IPS = [ "127.0.0.1", @@ -86,12 +87,22 @@ DATABASES = { "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "ENGINE": "django.db.backends.postgresql", + "NAME": os.getenv("DB_NAME", "cinema"), + "USER": os.getenv("DB_USER", "postgres"), + "PASSWORD": os.getenv("DB_PASS", "postgres"), + "HOST": os.getenv("DB_HOST", "db"), + "PORT": os.getenv("DB_PORT", "5432"), } } +STATIC_URL = "/static/" +MEDIA_URL = "/media/" + +STATIC_ROOT = "/vol/web/static" +MEDIA_ROOT = "/vol/web/media" + # Password validation # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators @@ -131,10 +142,6 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ -STATIC_URL = "static/" - -MEDIA_URL = "/media/" -MEDIA_ROOT = BASE_DIR / "media" # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..edb81e5c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: "3.8" + +services: + db: + image: postgres:15-alpine + volumes: + - postgres_data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=cinema + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + + app: + build: . + ports: + - "8000:8000" + volumes: + - media_data:/vol/web/media + - static_data:/vol/web/static + environment: + - DB_HOST=db + - DB_NAME=cinema + - DB_USER=postgres + - DB_PASS=postgres + - ALLOWED_HOSTS=127.0.0.1,localhost + - DEBUG=False + depends_on: + - db + +volumes: + postgres_data: + media_data: + static_data: diff --git a/requirements.txt b/requirements.txt index 2dc12c67..2536f121 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ djangorestframework djangorestframework-simplejwt drf-spectacular Pillow +psycopg2-binary>=2.9.9 diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100644 index 00000000..9c234249 --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +chown -R django-user:django-user /vol/web + +exec gosu django-user python manage.py wait_for_db && \ + python manage.py migrate && \ + python manage.py collectstatic --no-input && \ + python manage.py runserver 0.0.0.0:8000