Skip to content

Commit 25732bc

Browse files
committed
deploy(cloud-run): migrate from Fly.io to GCP Cloud Run, port settings
- Dockerfile: rebuild as linux/amd64 two-stage image; remove Fly volume mounts and gosu; collectstatic at build time with placeholder SECRET_KEY - settings.py: remove IS_FLY/FLY_APP_NAME detection; DATABASE_URL-first with SQLite fallback for local dev; Cloud Run ALLOWED_HOSTS via K_SERVICE; CSRF origins updated; MEDIA_ROOT simplified - data.json: full SQLite fixture dump (72 objects) for Neon blog_db seeding - portfolio: css, template, and templatetag improvements from prior work
1 parent 836d502 commit 25732bc

8 files changed

Lines changed: 1272 additions & 64 deletions

File tree

Dockerfile

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,46 @@
1-
# Stage 1: Build stage
2-
FROM python:3.11-slim as builder
1+
FROM --platform=linux/amd64 python:3.11-slim AS builder
32

43
WORKDIR /app
54

6-
# Install build dependencies
75
RUN apt-get update && apt-get install -y --no-install-recommends \
86
build-essential \
97
libpq-dev \
108
&& rm -rf /var/lib/apt/lists/*
119

12-
# Create virtual environment
1310
RUN python -m venv /opt/venv
1411
ENV PATH="/opt/venv/bin:$PATH"
1512

16-
# Install Python dependencies
1713
COPY requirements.txt .
1814
RUN pip install --no-cache-dir --upgrade pip && \
1915
pip install --no-cache-dir -r requirements.txt && \
2016
pip install --no-cache-dir gunicorn dj-database-url
2117

22-
# Stage 2: Production stage
23-
FROM python:3.11-slim
18+
FROM --platform=linux/amd64 python:3.11-slim
2419

25-
# Set environment variables
2620
ENV PYTHONDONTWRITEBYTECODE=1 \
2721
PYTHONUNBUFFERED=1 \
2822
PATH="/opt/venv/bin:$PATH" \
2923
PORT=8000
3024

3125
WORKDIR /app
3226

33-
# Install runtime dependencies only
3427
RUN apt-get update && apt-get install -y --no-install-recommends \
3528
libpq5 \
3629
&& rm -rf /var/lib/apt/lists/* \
3730
&& useradd --create-home --shell /bin/bash appuser
3831

39-
# Copy virtual environment from builder
4032
COPY --from=builder /opt/venv /opt/venv
41-
42-
# Copy application code
4333
COPY --chown=appuser:appuser . .
4434

45-
# Create directories for static and media files
46-
RUN mkdir -p /app/staticfiles /data/media && \
47-
chown -R appuser:appuser /app /data
35+
RUN mkdir -p /app/staticfiles /app/uploads && \
36+
chown -R appuser:appuser /app
4837

49-
# Collect static files
50-
RUN python manage.py collectstatic --noinput --clear
38+
# collectstatic needs a non-empty SECRET_KEY at build time; not the production key
39+
RUN SECRET_KEY=collectstatic-placeholder python manage.py collectstatic --noinput --clear
5140

52-
# Switch to non-root user
5341
USER appuser
54-
55-
# Expose port
5642
EXPOSE 8000
5743

58-
# Run gunicorn
59-
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "--threads", "4", "--worker-class", "gthread", "--worker-tmp-dir", "/dev/shm", "--access-logfile", "-", "--error-logfile", "-", "blog.wsgi:application"]
44+
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "--threads", "4", \
45+
"--worker-class", "gthread", "--worker-tmp-dir", "/dev/shm", \
46+
"--access-logfile", "-", "--error-logfile", "-", "blog.wsgi:application"]

blog/settings.py

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,26 @@
33

44
BASE_DIR = Path(__file__).resolve().parent.parent
55

6-
# Detect Fly.io environment
7-
FLY_APP_NAME = os.getenv('FLY_APP_NAME')
8-
IS_FLY = FLY_APP_NAME is not None
9-
106
SECRET_KEY = os.getenv('SECRET_KEY')
117

128
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
139

1410
ALLOWED_HOSTS = [
1511
'localhost',
1612
'127.0.0.1',
17-
'.fly.dev',
18-
'.internal',
1913
'blog.ericgitangu.com',
20-
'deveric-blog.azurewebsites.net',
2114
'developer.ericgitangu.com',
15+
'.run.app',
2216
]
2317

24-
# Allow Fly.io internal health check IPs
25-
if IS_FLY:
18+
# Cloud Run sets K_SERVICE; allow all on managed infra for health checks
19+
if os.getenv('K_SERVICE'):
2620
ALLOWED_HOSTS.append('*')
2721

2822
CSRF_TRUSTED_ORIGINS = [
29-
'https://*.fly.dev',
3023
'https://blog.ericgitangu.com',
31-
'https://deveric-blog.azurewebsites.net',
3224
'https://developer.ericgitangu.com',
25+
'https://*.run.app',
3326
]
3427

3528
INSTALLED_APPS = [
@@ -73,27 +66,23 @@
7366

7467
WSGI_APPLICATION = 'blog.wsgi.application'
7568

76-
# Database configuration
77-
if IS_FLY:
78-
# Fly.io provides DATABASE_URL
79-
import dj_database_url
69+
# Database — DATABASE_URL takes precedence (Cloud Run / CI); fallback to SQLite for local dev
70+
import dj_database_url as _dj_db_url
71+
72+
_db_url = os.getenv('DATABASE_URL')
73+
if _db_url:
8074
DATABASES = {
81-
'default': dj_database_url.config(
82-
default=os.getenv('DATABASE_URL'),
75+
'default': _dj_db_url.config(
76+
default=_db_url,
8377
conn_max_age=600,
8478
conn_health_checks=True,
8579
)
8680
}
8781
else:
88-
# Local development or Azure config
8982
DATABASES = {
9083
'default': {
91-
'ENGINE': 'django.db.backends.postgresql_psycopg2',
92-
'NAME': os.getenv('DBNAME'),
93-
'USER': os.getenv('DBUSER'),
94-
'PASSWORD': os.getenv('DBPASS'),
95-
'HOST': os.getenv('DBHOST'),
96-
'PORT': os.getenv('DBPORT'),
84+
'ENGINE': 'django.db.backends.sqlite3',
85+
'NAME': BASE_DIR / 'db.sqlite3',
9786
}
9887
}
9988

@@ -124,10 +113,7 @@
124113
}
125114

126115
# Media files
127-
if IS_FLY:
128-
MEDIA_ROOT = '/data/media' # Fly Volume mount point
129-
else:
130-
MEDIA_ROOT = BASE_DIR / 'uploads'
116+
MEDIA_ROOT = BASE_DIR / 'uploads'
131117

132118
MEDIA_URL = '/media/'
133119

@@ -136,8 +122,7 @@
136122
# Security settings for production
137123
if not DEBUG:
138124
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
139-
# SSL redirect handled by Fly.io proxy (force_https in fly.toml)
140-
# Disable Django's redirect to allow internal health checks
125+
# Cloud Run handles TLS termination upstream; Django must not redirect
141126
SECURE_SSL_REDIRECT = False
142127
SESSION_COOKIE_SECURE = True
143128
CSRF_COOKIE_SECURE = True

0 commit comments

Comments
 (0)