Deployment guide

Django 5

Overview

Django is the leading open-source Python web framework. It includes an ORM, an admin UI, authentication, forms, migrations and REST integrations.

The SecureAnyCloud image installs Django 5.2.13 (the current LTS, supported through April 2028) into a Python 3.12 virtualenv. gunicorn 23 (the WSGI app server) and nginx (the reverse proxy and static-file server) front it, and PostgreSQL 16 backs it. A SecureAnyCloud starter project lives at /opt/django/sac with a core app. A Django superuser is created at first boot, so the admin UI is reachable as soon as the VM finishes booting.

What you get:

  • Django 5.2.13 LTS (BSD 3-Clause) in a Python 3.12 virtualenv at /opt/django/venv
  • gunicorn 23 binding to /run/gunicorn/sac.sock (3 workers by default)
  • nginx reverse proxy on TCP :80 (proxy_pass to the gunicorn socket, serving static files at /static/)
  • PostgreSQL 16 from Ubuntu noble main, with a per-VM SecureAnyCloud role and a SecureAnyCloud database
  • psycopg 3.2 (pool + binary)
  • SecureAnyCloud starter project at /opt/django/sac with a core app
  • A per-VM SECRET_KEY (50 characters), database password (32 hex characters) and Django superuser password (32 hex characters), all rotated at first boot
  • Systemd units gunicorn.service and django-firstboot.service, plus the standard nginx.service and postgresql.service
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key, and a VNet and subnet.

Standard_B2s (4 GB RAM) is comfortable for a 3-worker gunicorn plus Postgres.

Configure the NSG to allow inbound 22/tcp from your management CIDR and 80/tcp from any client CIDR that needs the Django UI.

Step 1-3: Deploy and connect over SSH

ssh azureuser@<vm-ip>
4

Check service status and versions

sudo systemctl is-active gunicorn.service nginx.service postgresql.service /opt/django/venv/bin/python -c "import django; print('Django', django.get_version())"
5

Read the per-VM credentials

sudo cat /stage/scripts/django-credentials.log

Note DJANGO_URL, DJANGO_ADMIN_URL, DJANGO_ADMIN_USER and DJANGO_ADMIN_PASSWORD. The file also contains the database password and the per-VM SECRET_KEY.

6

Check HTTP health and static files

curl -sf -o /dev/null -w 'admin login: HTTP %{http_code}\n' http://127.0.0.1/admin/login/
curl -sf -o /dev/null -w 'static base.css: HTTP %{http_code}\n' http://127.0.0.1/static/admin/css/base.css

The first call reaches gunicorn through nginx. The second call proves that nginx serves collected static files directly, which is faster than going through Django.

7

Django admin: sign in

Browse to http://<vm-ip>/admin/ and sign in as SecureAnyCloud with the password from Step 5.

8

Django admin: site administration

After you sign in, the Site Administration page shows the auto-discovered admin models. These are Authentication and Authorization (Users, Groups) and any apps you add to INSTALLED_APPS.

9

Django admin: users list

Click Users to see the SecureAnyCloud superuser. Add more users from this page, or run manage.py createsuperuser.

10

Run Django management commands

set -a; . /etc/django/sac.env; export DJANGO_SETTINGS_MODULE=sac.settings; set +a cd /opt/django/sac
sudo -E -u django /opt/django/venv/bin/python manage.py migrate --noinput | tail -3 sudo -E -u django /opt/django/venv/bin/python manage.py showmigrations --plan | tail -10

The first command is idempotent, so already-applied migrations are no-ops. The second command prints the migration plan ordering.

11

Add a Django app

Replace <app-name> with the name of your app:

cd /opt/django/sac
sudo -u django /opt/django/venv/bin/python manage.py startapp <app-name>

Edit sac/settings.py to add '<app-name>' to INSTALLED_APPS, and define your models in <app-name>/models.py. Then run makemigrations <app-name> && migrate.

12

Tune the gunicorn workers

Edit /etc/django/sac.env to raise GUNICORN_WORKERS, and then restart gunicorn:

sudo systemctl restart gunicorn.service

Defaults on Standard_B2s:

  • GUNICORN_WORKERS: 3
  • DJANGO_DEBUG: False
  • DJANGO_ALLOWED_HOSTS: localhost,127.0.0.1,<public-ip>
  • DB: PostgreSQL 16, role SecureAnyCloud, db SecureAnyCloud

For production raise workers to 2 × CPU + 1 (e.g. 9 on a D4s_v5).

13

Add your domain to ALLOWED_HOSTS

Edit /etc/django/sac.env and add your domain to the comma-separated list:

DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,<vm-ip>,apps.example.com

Then restart gunicorn:

sudo systemctl restart gunicorn.service
14

Install a Let's Encrypt certificate

Point the DNS A record of your domain at this VM, and then run:

sudo apt-get install -y python3-certbot-nginx
sudo certbot --nginx -d apps.example.com -m you@example.com --agree-tos --non-interactive --redirect

Certbot edits /etc/nginx/sites-available/django-sac to add the SSL server block and a :80 → :443 redirect, and then reloads nginx. The certbot.timer systemd unit handles renewal and runs twice daily.

15

Backups

Take a consistent backup while the services keep running. Dump the database with pg_dump, and archive the media/ upload directory:

PASS=$(sudo grep '^DJANGO_DB_PASSWORD=' /stage/scripts/django-credentials.log | cut -d= -f2-) PGPASSWORD="$PASS" pg_dump -h 127.0.0.1 -U sac sac | gzip > /var/backups/sac-db-$(date +%F).sql.gz
sudo tar czf /var/backups/sac-media-$(date +%F).tgz -C /opt/django/sac media

Copy /var/backups to Azure Blob Storage (az storage blob upload-batch) periodically for off-VM retention.

16

Read logs and troubleshoot

sudo journalctl -u gunicorn.service --no-pager -n 80
sudo journalctl -u nginx.service --no-pager -n 30
sudo journalctl -u postgresql.service --no-pager -n 30
sudo tail -50 /var/log/nginx/access.log
sudo tail -50 /var/log/nginx/error.log

Django app errors are surfaced through gunicorn's --access-logfile - and --error-logfile -, which both go to the systemd journal. For more verbose logs, set DJANGO_DEBUG=true in /etc/django/sac.env. Use this setting for development only.

Security

  • DEBUG=False by default, so the image is production-safe out of the box
  • ALLOWED_HOSTS auto-populated with the public IP at first boot
  • CSRF_TRUSTED_ORIGINS derived from ALLOWED_HOSTS
  • Per-VM SECRET_KEY (50 chars from openssl rand)
  • gunicorn binds to a Unix socket (not exposed to the network)
  • PostgreSQL listens on 127.0.0.1:5432 (loopback only)
  • Per-VM database password (32 hex characters)
  • Per-VM Django superuser with a 32-hex-character password
  • Restrict NSG inbound on :80 to the IP ranges that need the Django app
  • Install python3-certbot-nginx for Let's Encrypt automation (Step 14)

Support

SecureAnyCloud provides 24/7/365 expert technical support. Guaranteed response within 24 hours, one hour average for critical issues. Contact sales@secureanycloud.com.

Deploy on Azure

Launch Django 5.2 LTS on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure