Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

django-fresh

Django project template for deployment behind Traefik.

Next step: The steps from "Create your Django app" onwards:

https://brntn.me/blog/six-things-i-do-every-time-i-start-a-django-project/

Local Development

Use python src/manage.py runserver — no Docker needed. Copy src/conf/local_settings.py.template to src/conf/local_settings.py and customise. Copy src/.env.template to src/.env and customise. Run migrations, create superuser.

Deployment with Docker and Traefik

This guide walks through deploying a Django app behind Traefik on a VPS. Traefik handles routing and automatic TLS certificates via Let's Encrypt. The Django app runs in a container with uWSGI serving static files, authenticated media, and the app itself.

User vs root: Git commands (clone, pull, etc.) must be run as the non-privileged user that owns the app files. Docker commands must be run as root (e.g. via sudo or a root shell).

1. Get Traefik running

Go to GitHub and create a PAT (Personal Access Token) to clone docker-traefik with. Give it read-only access to the contents and metadata permissions, and restrict it to the docker-traefik repo.

Now, clone (as the non-privileged user):

cd /home/user
git clone https://CapnKernel:PAT@github.com/CapnKernel/docker-traefik
cd docker-traefik

Then start Traefik (as root):

docker compose up -d

Verify Traefik is running:

docker ps
# Look for the traefik container, ports 80, 443, and 8080

Traefik will auto-discover other containers on the shared_docker_network network via Docker labels created by apps.

Docker commands for traefik

(Run all of these from ~user/docker-traefik)

# See Traefik logs
docker compose logs -f

# See Traefik status
docker compose ps

# Run / start Traefik
docker compose up -d

# Restart Traefik
docker compose restart

# Stop Traefik
docker compose down

# Rebuild and run Traefik (e.g. after config changes)
docker compose up -d --force-recreate

2. Clone and create your app

Create a new GitHub repository using django-fresh as a template:

  1. Go to https://github.com/CapnKernel/django-fresh

  2. Click "Use this template""Create a new repository"

  3. Name it (e.g. myapp)

  4. Create a PAT for authentication, with read-only access to the content and metadata permissions, and restricted to the myapp repo.

  5. Clone it onto the VPS:

    # On the VPS, as the user who will own the app files:
    git clone https://CapnKernel:PAT@github.com/CapnKernel/myapp.git
    cd ~/myapp

Tip: To make copying commands from this readme easier, search/replace myapp with the name of your app.

3. Configure the Django app

Edit docker-compose.yml in your app repo. Set the environment variables and Traefik labels for your deployment.

Choose the example that matches your deployment pattern.

Example A: top-level site at https://myapp.afork.com/

name: myapp
services:
  myapp:
    build: .
    image: myapp:latest
    pull_policy: build
    container_name: myapp_web
    volumes:
      - sqlite:/data/db
      - media:/data/media
      - static:/app/static
      - env:/data/env
    environment:
      - DJANGO_SETTINGS_MODULE=conf.settings
      - SITE_NAME=MyApp
      - ALLOWED_HOSTS=myapp.afork.com,localhost
      - SCRIPT_NAME=
      - CSRF_TRUSTED_ORIGINS=https://myapp.afork.com
      - EMAIL_DEFAULT_FROM=noreply@afork.com
      - EMAIL_HOST=
      - EMAIL_PORT=
      - EMAIL_HOST_USER=
      - EMAIL_HOST_PASSWORD=
      - DBBACKUP_HOSTNAME=myapp
    networks:
      - shared_docker_network
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.rule=Host(`myapp.afork.com`)"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.tls=true"
      - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
      - "traefik.http.services.myapp.loadbalancer.server.port=8000"

volumes:
  sqlite:
  media:
  static:
  env:

networks:
  shared_docker_network:
    external: true

Example B: subpath site at https://vps.afork.com/myapp

name: myapp
services:
  myapp:
    build: .
    image: myapp:latest
    pull_policy: build
    container_name: myapp_web
    volumes:
      - sqlite:/data/db
      - media:/data/media
      - static:/app/static
      - env:/data/env
    environment:
      - DJANGO_SETTINGS_MODULE=conf.settings
      - SITE_NAME=MyApp
      - ALLOWED_HOSTS=vps.afork.com,localhost
      - SCRIPT_NAME=/myapp
      - CSRF_TRUSTED_ORIGINS=https://vps.afork.com
      - EMAIL_DEFAULT_FROM=noreply@afork.com
      - EMAIL_HOST=
      - EMAIL_PORT=
      - EMAIL_HOST_USER=
      - EMAIL_HOST_PASSWORD=
      - DBBACKUP_HOSTNAME=myapp
    networks:
      - shared_docker_network
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.rule=Host(`vps.afork.com`) && PathPrefix(`/myapp`)"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.tls=true"
      - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
      - "traefik.http.services.myapp.loadbalancer.server.port=8000"

volumes:
  sqlite:
  media:
  static:
  env:

networks:
  shared_docker_network:
    external: true

Key configuration points:

Setting Top-level Subpath Reason
SCRIPT_NAME (empty) /myapp Tells Django the URL prefix; uWSGI strips it from PATH_INFO so Django sees bare paths
ALLOWED_HOSTS myapp.afork.com,localhost vps.afork.com,localhost Must match the Host header Traefik forwards
CSRF_TRUSTED_ORIGINS https://myapp.afork.com https://vps.afork.com Required for POST requests over HTTPS
Traefik rule Host(\myapp.afork.com`)` Host(\vps.afork.com`) && PathPrefix(`/myapp`)` Routes matching requests to this container

4. Deploy the app

(as root):

cd ~/myapp
docker compose up -d

This will:

  • Build the Docker image (install Python deps, copy code, set up uWSGI)
  • Create the named volumes (sqlite, media, static, env)
  • Start the container on shared_docker_network
  • Run migrations and collect static files on startup
  • Traefik detects the new container via labels and starts routing traffic

5. Create a superuser

docker compose exec myapp python manage.py createsuperuser --email admin@afork.com

6. Verify

Use curl to check the site responds:

Page Top-level site Subpath site
Main curl -sI https://myapp.afork.com/ | head -5 curl -sI https://vps.afork.com/myapp/ | head -5
Admin curl -sI https://myapp.afork.com/office/ | head -5 curl -sI https://vps.afork.com/myapp/office/ | head -5

Expected: a 200 or 302 (redirect to login) response.

With examples:

  • App (top-level): https://myapp.afork.com/
  • Admin (top-level): https://myapp.afork.com/office/
  • App (subpath): https://vps.afork.com/myapp/
  • Admin (subpath): https://vps.afork.com/myapp/office/
  • Traefik dashboard: http://localhost:8080/ (use ssh port forwarding)

Deployment URL patterns

Pattern SCRIPT_NAME Traefik rule
Top-level site (empty) Host(\myapp.afork.com`)`
Subpath site /myapp Host(\vps.afork.com`) && PathPrefix(`/myapp`)`

For a top-level site (e.g. https://myapp.afork.com/), set SCRIPT_NAME= (empty) and use a simple Host() rule.

For a subpath site (e.g. https://vps.afork.com/myapp), set SCRIPT_NAME=/myapp and use Host() && PathPrefix(/myapp). uWSGI's --mount + --manage-script-name handles prefix stripping.

Configuration Reference

Variable Description
DJANGO_SETTINGS_MODULE Always conf.settings
SITE_NAME Display name in templates
ALLOWED_HOSTS Comma-separated hostnames
SCRIPT_NAME URL prefix (empty for root, /myapp for subpath)
CSRF_TRUSTED_ORIGINS Comma-separated origins
EMAIL_DEFAULT_FROM From address for emails
EMAIL_HOST SMTP server
EMAIL_PORT SMTP port
EMAIL_HOST_USER SMTP username
EMAIL_HOST_PASSWORD SMTP password
DBBACKUP_HOSTNAME Instance identifier in backup filenames

Docker management

All docker commands below must be run as root (e.g. via sudo or a root shell). Git commands (clone, pull, etc.) should be run as the non-privileged user.

The app uses four named volumes:

Volume name Mount point Access Purpose
static /app/static/ ro Collected static files (CSS, JS, images)
sqlite /data/db/ rw SQLite database file (db.sqlite3)
media /data/media/ rw User-uploaded files
env /data/env/ rw Persistent secrets (e.g. secret_key.txt)

Shell browsing

To browse the volumes, open a shell inside the container:

docker compose exec myapp bash

Use standard commands to inspect:

# List files in a volume
ls -la /data/db/

# Read a file
cat /data/env/secret_key.txt

# Check database size
du -sh /data/db/

Copying files in and out

# Copy a file into the container (e.g. restore a database backup)
docker cp ./backup.sql myapp_web:/data/db/

# Copy a file out of the container (e.g. download a media file)
docker cp myapp_web:/data/media/photo.jpg ./photo.jpg

# Copy the entire database out
docker cp myapp_web:/data/db/db.sqlite3 ./db.sqlite3

Other commands

# See app logs
docker compose logs -f

# See app status
docker compose ps

# Stop the app (keeps volumes, network)
docker compose down

# Restart the app
docker compose restart

# Run / start the app (builds image if needed)
docker compose up -d

# Rebuild the image and start (after code changes)
docker compose up -d --build

# Run Django management commands
docker compose exec myapp python manage.py check --deploy
docker compose exec myapp python manage.py createsuperuser  --email admin@afork.com
docker compose exec myapp python manage.py dumpdata --indent 2 > backup.json

# Backup the SQLite database
docker compose exec myapp sh -c "cp /data/db/db.sqlite3 /data/db/db.sqlite3.bak"

# Restore the SQLite database from a local backup
docker cp ./db.sqlite3 myapp_web:/data/db/db.sqlite3
docker compose restart myapp

# Remove everything (volumes too — deletes database, media, secrets)
docker compose down -v

Clean everything

This is the nuke everything from orbit option. It wipes out all images, containers, volumes and build objects for all app, and traefik too. Run as root, works from any directory.

# Nuke
docker stop $(docker ps -aq); docker rm $(docker ps -aq); docker rmi -f $(docker images -q); docker volume rm $(docker volume ls -q); docker system prune -af --volumes
# Verify
docker ps -a; echo '---'; docker images; echo '---'; docker volume ls; echo '---'; docker system df

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages