- GitHub for code
- Heroku pipeline with one production app
- Heroku Postgres
- Heroku Redis
- Linked to GitHub repo
- Add environment variables for
ENVIRONMENT=live
,DISABLE_COLLECTSTATIC=1
, andSECRET_KEY
- Add
pgbouncer
andnginx
buildpacks
- Cloudflare for SSL and CNAME pointing to Heroku app domain
- Force SSL with a Cloudflare rule
- Namecheap for the domain
- Point nameservers to Cloudflare
- Sentry for error logging
- Panelbear and Plausible for privacy-focused analytics
- UptimeRobot to get alerts for when the site goes down
Last active
November 13, 2022 16:31
-
-
Save adamghill/9c11687eb87e98951adb11c4dacdb709 to your computer and use it in GitHub Desktop.
Settings, files, and a checklist to deploy a Django app to Heroku with nginx + gunicorn + postgres + redis and using `poetry` for dependencies.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def when_ready(server): | |
# touch app-initialized when ready | |
open("/tmp/app-initialized", "w").close() | |
bind = "unix:///tmp/nginx.socket" | |
workers = 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rem this lives in the `bin` directory (i.e. bin/post_compile) | |
#!/usr/bin/env bash | |
set -eo pipefail | |
indent() { | |
sed "s/^/ /" | |
} | |
puts-step() { | |
echo "-----> $@" | |
} | |
/app/.heroku/python/bin/python -m pip install --upgrade pip | |
puts-step "Installing dependencies with poetry..." | |
poetry config virtualenvs.create false 2>&1 | indent | |
poetry install --no-dev 2>&1 | indent | |
puts-step "Collect static files (part 1)..." | |
python manage.py collectstatic --noinput 2>&1 | indent | |
puts-step "Compress files..." | |
python manage.py compress --force 2>&1 | indent | |
puts-step "Collect static files (part 2)..." | |
python manage.py collectstatic --noinput 2>&1 | indent |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
web: python manage.py collectstatic --noinput && bin/start-pgbouncer bin/start-nginx gunicorn -c gunicorn.conf.py project.wsgi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
poetry==1.1.6 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
python-3.7.12 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
from pathlib import Path | |
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev") | |
# Regular dev settings go here | |
if ENVIRONMENT == "live": | |
import dj_database_url | |
import sentry_sdk | |
from sentry_sdk.integrations.django import DjangoIntegration | |
sentry_sdk.init( | |
dsn=os.environ.get("SENTRY_DSN"), | |
integrations=[DjangoIntegration()], | |
traces_sample_rate=1.0, | |
send_default_pii=True, | |
) | |
DEBUG = False | |
COMPRESS_OFFLINE = True | |
ALLOWED_HOSTS += [ | |
"ADD-DOMAINS-HERE" | |
] | |
CACHES = { | |
"default": { | |
"BACKEND": "django_redis.cache.RedisCache", | |
"LOCATION": os.environ.get("REDIS_URL"), | |
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, | |
} | |
} | |
DATABASES["default"] = dj_database_url.config(conn_max_age=600) | |
LOGGING = { | |
"version": 1, | |
"disable_existing_loggers": False, | |
"formatters": { | |
"console": { | |
"format": "[%(asctime)s][%(levelname)s] %(name)s " | |
"%(filename)s:%(funcName)s:%(lineno)d | %(message)s", | |
"datefmt": "%H:%M:%S", | |
}, | |
"verbose": { | |
"format": ( | |
"%(asctime)s [%(process)d] [%(levelname)s] " | |
+ "pathname=%(pathname)s lineno=%(lineno)s " | |
+ "funcname=%(funcName)s %(message)s" | |
), | |
"datefmt": "%Y-%m-%d %H:%M:%S", | |
}, | |
"simple": {"format": "%(levelname)s %(message)s"}, | |
}, | |
"handlers": { | |
"null": {"level": "DEBUG", "class": "logging.NullHandler"}, | |
"console": { | |
"level": "DEBUG", | |
"class": "logging.StreamHandler", | |
"formatter": "verbose", | |
}, | |
}, | |
"loggers": { | |
"": {"handlers": ["console"], "level": "INFO", "propagate": False}, | |
"testlogger": {"handlers": ["console"], "level": "INFO"}, | |
"deploylogs": {"level": "WARNING", "propagate": True}, | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment