Applies to: LumaDock VPS • Ubuntu 22.04 LTS (Jammy)
This guide shows how to put an existing Django project folder (with manage.py
and requirements.txt
) online. You’ll upload/clone your code, set environment variables, install dependencies in a virtualenv, run Django via Gunicorn, and serve it publicly with Nginx—plus optional HTTPS (Let’s Encrypt) and PostgreSQL.
Requirements
- A LumaDock VPS running Ubuntu 22.04 LTS
- Root SSH access (or a sudo-enabled user)
- An existing Django project with
requirements.txt
(and optional.env
) - (Recommended) A domain pointed to your VPS IP
1) Prepare the server
ssh root@YOUR_SERVER_IP
apt update && apt -y upgrade
apt install -y python3 python3-venv python3-pip python3-dev build-essential nginx git
Optional: create a non-root user: adduser admin && usermod -aG sudo admin
then ssh admin@YOUR_SERVER_IP
.
2) Upload or clone your project
We’ll place the app under /opt/djangoapp
(adjust paths to your preference).
mkdir -p /opt/djangoapp
cd /opt/djangoapp
# Option A: clone from Git
git clone https://YOUR_REPO_URL.git .
# Option B: upload via SFTP/rsync into this folder
Ensure your manage.py
lives at /opt/djangoapp/manage.py
.
3) Create a virtualenv & install dependencies
cd /opt/djangoapp
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
If your project uses a specific Gunicorn / ASGI server, ensure it’s listed in requirements.txt
(e.g., gunicorn
).
4) Configure environment variables & Django settings
Set a few production basics:
- ALLOWED_HOSTS → add your domain and/or server IP
- DEBUG →
False
in production - SECRET_KEY → secure random string (store in environment, not in Git)
- DATABASE_URL or DB settings (if not using SQLite)
# Example: /opt/djangoapp/.env (if using python-dotenv/django-environ)
DJANGO_DEBUG=False
DJANGO_SECRET_KEY=change_me_to_a_strong_secret
DJANGO_ALLOWED_HOSTS=yourdomain.com, YOUR_SERVER_IP
Load your .env
in settings.py
(via django-environ
or similar) or set environment variables in the systemd unit (see Gunicorn step).
5) Database & migrations
If you use SQLite, you can skip installation. For PostgreSQL/MySQL, install and configure first, then:
cd /opt/djangoapp
source venv/bin/activate
python manage.py migrate
python manage.py createsuperuser # optional
If using PostgreSQL, make sure psycopg2-binary
(or psycopg
) is installed and your DB settings are correct.
6) Static & media files
# settings.py (example)
STATIC_URL = "static/"
STATIC_ROOT = "/opt/djangoapp/static"
MEDIA_URL = "media/"
MEDIA_ROOT = "/opt/djangoapp/media"
python manage.py collectstatic --noinput
We’ll configure Nginx to serve /static/
and /media/
directly.
7) Run with Gunicorn (systemd service)
Create a systemd unit to keep Gunicorn running in the background.
nano /etc/systemd/system/gunicorn-djangoapp.service
[Unit]
Description=Gunicorn for Django (djangoapp)
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/djangoapp
Environment="DJANGO_SETTINGS_MODULE=yourproject.settings"
Environment="DJANGO_SECRET_KEY=change_me"
Environment="DJANGO_DEBUG=False"
Environment="DJANGO_ALLOWED_HOSTS=yourdomain.com,YOUR_SERVER_IP"
Environment="PATH=/opt/djangoapp/venv/bin"
ExecStart=/opt/djangoapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 yourproject.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now gunicorn-djangoapp
systemctl status gunicorn-djangoapp
8) Nginx reverse proxy
- Create a server block:
nano /etc/nginx/sites-available/djangoapp
server { listen 80; server_name YOUR_DOMAIN_OR_IP; client_max_body_size 20M; location /static/ { alias /opt/djangoapp/static/; } location /media/ { alias /opt/djangoapp/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
- Enable and reload:
ln -s /etc/nginx/sites-available/djangoapp /etc/nginx/sites-enabled/ nginx -t && systemctl restart nginx
Open http://YOUR_DOMAIN_OR_IP
— your app should be live.
9) (Optional) HTTPS with Let’s Encrypt
- Install Certbot:
apt install -y certbot python3-certbot-nginx
- Issue and auto-configure:
certbot --nginx -d YOUR_DOMAIN -m you@example.com --agree-tos --redirect
- Verify auto-renew:
systemctl list-timers | grep certbot
10) Updating your app (zero-downtime-ish)
- Pull code / upload new release:
cd /opt/djangoapp git pull # or upload new files
- Reinstall deps if changed:
source venv/bin/activate pip install -r requirements.txt
- Run migrations & collectstatic:
python manage.py migrate python manage.py collectstatic --noinput
- Graceful reload:
systemctl reload nginx systemctl restart gunicorn-djangoapp
Troubleshooting
Symptom | Likely cause | Fix |
---|---|---|
400 Bad Request from Django | ALLOWED_HOSTS missing domain/IP |
Add domain/IP to ALLOWED_HOSTS and restart Gunicorn. |
403/404 for static | Static not collected or wrong Nginx alias | Set STATIC_ROOT , run collectstatic , verify location /static/ alias path. |
502 Bad Gateway | Gunicorn down or wrong bind/port | Check systemctl status gunicorn-djangoapp , logs, ensure proxy_pass → 127.0.0.1:8000 . |
Permission denied on static/media | Ownership/permissions | Set proper owner (e.g., www-data ) and safe permissions—see Appendix. |
No HTTPS | Cert not issued / DNS not pointing | Point domain to VPS IP, open 80/443, rerun certbot --nginx . |
Appendix: Useful commands & permissions
Service & logs
journalctl -u gunicorn-djangoapp -e
journalctl -u nginx -e
systemctl restart gunicorn-djangoapp nginx
Ownership & permissions (example)
# App directory owned by web user/group
chown -R www-data:www-data /opt/djangoapp
# Directories readable/executable
find /opt/djangoapp -type d -exec chmod 755 {} \;
# Files readable
find /opt/djangoapp -type f -exec chmod 644 {} \;
# Allow manage.py to execute (if needed)
chmod +x /opt/djangoapp/manage.py