diff --git a/.env b/.env deleted file mode 100644 index 7e893174..00000000 --- a/.env +++ /dev/null @@ -1,6 +0,0 @@ -DBNAME=app -DBHOST=localhost -DBUSER=app_user -DBPASS=app_password -CACHELOCATION=redis://redis:6379/0 -SECRET_KEY=secret_key diff --git a/.github/workflows/start_msdocs-django.yml b/.github/workflows/start_msdocs-django.yml new file mode 100644 index 00000000..c86e50cf --- /dev/null +++ b/.github/workflows/start_msdocs-django.yml @@ -0,0 +1,81 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions +# More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions + +name: Build and deploy Python app to Azure Web App - msdocs-django + +on: + push: + branches: + - start + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read #This is required for actions/checkout + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python version + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Create and start virtual environment + run: | + python -m venv venv + source venv/bin/activate + + - name: Install dependencies + run: pip install -r requirements.txt + + # Optional: Add step to run tests here (PyTest, Django test suites, etc.) + + - name: Zip artifact for deployment + run: zip release.zip ./* -r + + - name: Upload artifact for deployment jobs + uses: actions/upload-artifact@v4 + with: + name: python-app + path: | + release.zip + !venv/ + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + contents: read #This is required for actions/checkout + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: python-app + + - name: Unzip artifact for deployment + run: unzip release.zip + + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_9BA1861B2903462589D964551B5B6FF3 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_F39E2916EAF1432388721E2E80E7E0F2 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_4063657E9CEA484CB505C2B6F9037DFE }} + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v3 + id: deploy-to-webapp + with: + app-name: 'msdocs-django' + slot-name: 'Production' + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9b957b1b..92a37e26 100644 --- a/.gitignore +++ b/.gitignore @@ -128,4 +128,5 @@ dmypy.json # Pyre type checker .pyre/ -.azure \ No newline at end of file +.azure +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..04ddccf2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# Stage 1: Base build stage +FROM python:3.13-slim AS builder + +# Create the app directory +RUN mkdir /app + +# Set the working directory +WORKDIR /app + +# Set environment variables to optimize Python +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Upgrade pip and install dependencies +RUN pip install --upgrade pip + +# Copy the requirements file first (better caching) +COPY requirements.txt /app/ + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Stage 2: Production stage +FROM python:3.13-slim + +RUN useradd -m -r appuser && \ + mkdir /app && \ + chown -R appuser /app + +# 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 /app + +# Copy application code +COPY --chown=appuser:appuser . . + +RUN python manage.py collectstatic --noinput + +# Set environment variables to optimize Python +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Switch to non-root user +USER appuser + +# Expose the application port +EXPOSE 8000 + +# Start the application using Gunicorn +CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "azureproject.wsgi:application"] diff --git a/azureproject/production.py b/azureproject/production.py index 76b6e1f0..0f89bc06 100644 --- a/azureproject/production.py +++ b/azureproject/production.py @@ -29,10 +29,10 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.environ['AZURE_POSTGRESQL_NAME'], - 'HOST': os.environ['AZURE_POSTGRESQL_HOST'], - 'USER': os.environ['AZURE_POSTGRESQL_USER'], - 'PASSWORD': os.environ['AZURE_POSTGRESQL_PASSWORD'], + 'NAME': os.environ['DATABASE_NAME'], + 'HOST': os.environ['DATABASE_HOST'], + 'USER': os.environ['DATABASE_USERNAME'], + 'PASSWORD': os.environ['DATABASE_PASSWORD'], } } diff --git a/azureproject/settings.py b/azureproject/settings.py index 5d890707..744af36c 100644 --- a/azureproject/settings.py +++ b/azureproject/settings.py @@ -21,12 +21,12 @@ # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv('SECRET_KEY') +SECRET_KEY = os.getenv('DJANGO_SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = bool(os.environ.get("DEBUG", default=0)) -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS","127.0.0.1").split(",") if 'CODESPACE_NAME' in os.environ: CSRF_TRUSTED_ORIGINS = [f'https://{os.getenv("CODESPACE_NAME")}-8000.{os.getenv("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN")}'] @@ -51,6 +51,7 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', ] SESSION_ENGINE = "django.contrib.sessions.backends.cache" @@ -92,14 +93,17 @@ # Configure Postgres database for local development # Set these environment variables in the .env file for this project. DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.environ.get('DBNAME'), - 'HOST': os.environ.get('DBHOST'), - 'USER': os.environ.get('DBUSER'), - 'PASSWORD': os.environ.get('DBPASS'), - } -} + 'default': { + 'ENGINE': 'django.db.backends.{}'.format( + os.getenv('DATABASE_ENGINE', 'sqlite3') + ), + 'NAME': os.getenv('DATABASE_NAME', 'polls'), + 'USER': os.getenv('DATABASE_USERNAME', 'myprojectuser'), + 'PASSWORD': os.getenv('DATABASE_PASSWORD', 'password'), + 'HOST': os.getenv('DATABASE_HOST', '127.0.0.1'), + 'PORT': os.getenv('DATABASE_PORT', 5432), + } + } # Password validation @@ -123,7 +127,7 @@ CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": os.environ.get('CACHELOCATION'), + "LOCATION": f"redis://{os.environ.get('REDIS_HOST', 'redis')}:{os.environ.get('REDIS_PORT', 6379)}/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", }, @@ -145,8 +149,10 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATICFILES_DIRS = (str(BASE_DIR.joinpath('static')),) -STATIC_URL = 'static/' +STATIC_URL = '/static/' +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..39e9d153 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,43 @@ +services: + db: + image: postgres:17 + environment: + POSTGRES_DB: ${DATABASE_NAME} + POSTGRES_USER: ${DATABASE_USERNAME} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + env_file: + - .env + + redis: + image: "redis:latest" + + django-web: + build: . + container_name: django-docker + ports: + - "8000:8000" + depends_on: + - db + - redis + environment: + DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY} + DEBUG: ${DEBUG} + DJANGO_LOGLEVEL: ${DJANGO_LOGLEVEL} + DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS} + DATABASE_ENGINE: ${DATABASE_ENGINE} + DATABASE_NAME: ${DATABASE_NAME} + DATABASE_USERNAME: ${DATABASE_USERNAME} + + DATABASE_PASSWORD: ${DATABASE_PASSWORD} + DATABASE_HOST: ${DATABASE_HOST} + DATABASE_PORT: ${DATABASE_PORT} + REDIS_HOST: redis + REDIS_PORT: 6379 + env_file: + - .env +volumes: + postgres_data: \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c17108d2..b31f0b8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,6 @@ psycopg2-binary==2.9.10 python-dotenv==1.0.1 whitenoise==6.9.0 django-redis==5.4.0 +asgiref==3.8.1 +sqlparse==0.5.2 +gunicorn==23.0.0