-
Notifications
You must be signed in to change notification settings - Fork 0
Developer Setup & Deployment Guide
This guide covers:
- Running the Django app locally (SQLite or Postgres)
- Running in production with Gunicorn, Nginx, and PostgreSQL
If your project structure differs, adjust paths accordingly. The commands assume a Unix-like shell (macOS/Linux) and Python 3.11+.
- Python 3.11+
- pip and virtualenv (or uv/poetry, if you prefer)
- Git
- Node & npm (only if the project has frontend builds; otherwise skip)
- PostgreSQL 14+ (optional for local; required for prod)
git clone https://github.com/dqprogramming/intrepid.git
cd intrepidCreate a virtual environment:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pipInstall Python deps:
pip install -r requirements.txtConfigure a settings.py file inside the intrepid application. In addition to establishing all Django basics, such as DATABASES, you will need a number of custom settings. These include
# Settings for use with Mailgun
USE_MAILGUN = True
MAILGUN_ACCESS_KEY = "KEY_HERE"
MAILGUN_SERVER_NAME = "SERVER_HERE"
MAILGUN_REQUIRE_TLS = True
ENABLE_ENHANCED_MAILGUN_FEATURES = True # Enables email tracking
FROM_EMAIL = "FROM@EMAIL.COM"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static-assets")]
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
SUMMERNOTE_THEME = "bs4"
SUMMERNOTE_CONFIG = {
# Using SummernoteWidget - iframe mode, default
"iframe": True,
# You can put custom Summernote settings
"summernote": {
# As an example, using Summernote Air-mode
"airMode": False,
# Change editor size
"width": "100%",
},
"toolbar": [
["style", ["style"]],
["font", ["bold", "italic", "underline", "clear"]],
["fontsize", ["fontsize"]],
["color", ["color"]],
["para", ["ul", "ol", "paragraph"]],
["height", ["height"]],
["table", ["table"]],
["insert", ["link", "picture", "hr", "video"]],
["view", ["fullscreen", "codeview"]],
["help", ["help"]],
],
}
BLEACH_DEFAULT_WIDGET = "django_summernote.widgets.SummernoteWidget"
BLEACH_STRIP_TAGS = True
BLEACH_ALLOWED_TAGS = [
"h1",
"h2",
"h3",
"h4",
"h5",
"p",
"br" "b",
"i",
"u",
"em",
"ul",
"li",
"ol",
"strong",
"a",
"table",
"tr",
"th",
"td",
"img",
"button",
"span",
"hr",
"iframe",
]
BLEACH_ALLOWED_ATTRIBUTES = ["href", "title", "style", "src", "alt"]
X_FRAME_OPTIONS = "SAMEORIGIN"
LOGIN_URL = "two_factor:login"
LOGIN_REDIRECT_URL = "profile"
LOGOUT_REDIRECT_URL = "two_factor:login"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
SITE_ID = 2
DATE_FORMAT = "Y-m-d"
DATETIME_FORMAT = "Y-m-d H:m:s"
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
}
# MATOMO
MATOMO_SITE_URL = ""
MATOMO_SITE_ID = None
# AWS backup IAM
AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY"
AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
AWS_DEFAULT_REGION = "eu-west-2"
BACKUP_BUCKET = "bucket_name"
# LANGUAGES
LANGUAGE_CODE = "en"
USE_I18N = True
gettext = lambda s: s
LANGUAGES = (
("de", gettext("German")),
("en", gettext("English")),
)
MODELTRANSLATION_DEFAULT_LANGUAGE = "en"
MODELTRANSLATION_LANGUAGES = ("en", "de")
MODELTRANSLATION_FALLBACK_LANGUAGES = ("en",)
MODELTRANSLATION_PREPOPULATE_LANGUAGE = "en"Update settings to use SQLite in settings.py if not already:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}Run migrations & start:
python manage.py migrate
python manage.py createsuperuser
python manage.py runserverApp is available at http://127.0.0.1:8000/
Create DB & user:
psql -U postgres -h localhost <<'SQL'
CREATE USER intrepid WITH PASSWORD 'intrepid';
CREATE DATABASE intrepid OWNER intrepid;
GRANT ALL PRIVILEGES ON DATABASE intrepid TO intrepid;
SQLEnsure your settings.py matches the DB credentials. Then:
python manage.py migrate
python manage.py createsuperuser
python manage.py runserverIf static files are configured:
python manage.py collectstatic --noinputFor dev, Django will serve static automatically when DEBUG=True. In production, Nginx serves static and media.
These steps assume Ubuntu 22.04 on a VM with a domain like
app.example.com.
sudo apt update
sudo apt install -y python3.11-venv python3-pip postgresql postgresql-contrib nginx pkg-config build-essential libpq-devsudo adduser --system --group --home /srv/intrepid intrepid
sudo mkdir -p /srv/intrepid/app
sudo chown -R intrepid:intrepid /srv/intrepidsudo -u intrepid bash -lc '
cd /srv/intrepid/app
python3 -m venv .venv
source .venv/bin/activate
git clone https://github.com/dqprogramming/intrepid.git src
cd src
pip install --upgrade pip
pip install -r requirements.txt
'sudo -u postgres psql <<'SQL'
CREATE USER intrepid WITH PASSWORD 'super-strong-password';
CREATE DATABASE intrepid OWNER intrepid;
REVOKE CONNECT ON DATABASE intrepid FROM PUBLIC;
GRANT CONNECT ON DATABASE intrepid TO intrepid;
SQLOptional: restrict schema permissions further as needed.
sudo -u intrepid bash -lc '
cd /srv/intrepid/app/src
source ../.venv/bin/activate
python manage.py migrate --noinput
python manage.py collectstatic --noinput
'Create /etc/systemd/system/intrepid-gunicorn.service:
[Unit]
Description=Gunicorn for Intrepid
After=network.target
[Service]
User=intrepid
Group=intrepid
WorkingDirectory=/srv/intrepid/app/src
Environment="DJANGO_SETTINGS_MODULE=project.settings"
EnvironmentFile=/srv/intrepid/.env
ExecStart=/srv/intrepid/app/.venv/bin/gunicorn project.wsgi:application --bind unix:/run/intrepid.sock --workers 3 --timeout 60 --access-logfile - --error-logfile -
RuntimeDirectory=intrepid
RuntimeDirectoryMode=0755
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.targetStart & enable:
sudo systemctl daemon-reload
sudo systemctl enable --now intrepid-gunicorn
sudo systemctl status intrepid-gunicornA Unix socket should appear at /run/intrepid.sock.
Create /etc/nginx/sites-available/intrepid:
server {
listen 80;
server_name app.example.com;
client_max_body_size 20M;
location /static/ {
alias /srv/intrepid/static/;
access_log off;
expires 30d;
}
location /media/ {
alias /srv/intrepid/media/;
access_log off;
expires 30d;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/intrepid.sock;
proxy_read_timeout 120;
proxy_connect_timeout 5;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Enable it:
sudo ln -s /etc/nginx/sites-available/intrepid /etc/nginx/sites-enabled/intrepid
sudo nginx -t
sudo systemctl reload nginxsudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com --redirect --email you@example.com --agree-tos --non-interactiveCertificates auto-renew via systemd timers.
sudo -u intrepid bash -lc '
cd /srv/intrepid/app/src
git pull
source ../.venv/bin/activate
pip install -r requirements.txt
python manage.py migrate --noinput
python manage.py collectstatic --noinput
'
sudo systemctl restart intrepid-gunicornLogs:
- Gunicorn:
journalctl -u intrepid-gunicorn -f - Nginx:
/var/log/nginx/access.log,/var/log/nginx/error.log
- Add a
/healthendpoint returning200 OK - Set
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") - Consider:
CSRF_TRUSTED_ORIGINS = ["https://app.example.com"]SESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = True-
SECURE_HSTS_SECONDS = 31536000(after HTTPS verified)
-
502 Bad Gateway → check Gunicorn service and
/run/intrepid.sock -
Static 404s → confirm
collectstaticand paths in Nginx - DB errors → verify env vars and Postgres access
-
Migrations → run
python manage.py showmigrations
If you prefer TCP over Unix sockets:
Gunicorn:
ExecStart=/srv/intrepid/app/.venv/bin/gunicorn project.wsgi:application --bind 127.0.0.1:8001 --workers 3Nginx:
proxy_pass http://127.0.0.1:8001;Restart Gunicorn and reload Nginx.
python manage.py migratepython manage.py createsuperuserpython manage.py collectstatic --noinputpython manage.py runserver 0.0.0.0:8000python manage.py shell