> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy Django on Upsun Cloud

> A step-by-step guide to deploy a Django application on Upsun Cloud, from a blank project to a live URL.


This guide walks you through deploying a Django application on Upsun Cloud step by step — from a blank project to a live URL.

If you have read the [Getting started guide](https://developer.upsun.com/docs/get-started/here), this page adds Django-specific detail that the generic guide skips.
If you haven't, no problem — this guide is self-contained.

## Before you begin

You need:

<ul><li>[Git](https://git-scm.com/downloads).
Git is the primary tool to manage everything your app needs to run.
Push commits to deploy changes and control configuration through YAML files.
These files describe your infrastructure, making it transparent and version-controlled.</li>
<li>An Upsun account.
If you don't already have one, [register for a trial account](https://auth.upsun.com/register).
You can sign up with an email address or an existing GitHub, Bitbucket, or Google account.
If you choose one of these accounts, you can set a password for your Upsun account later.</li>
<li>The [Upsun CLI](https://developer.upsun.com/cli.md).
This lets you interact with your project from the command line.
You can also do most things through the [Web Console](https://developer.upsun.com/docs/administration/web).</li></ul>

You also need **Python 3.9+** and `pip` installed locally.

## 1. Create a Django project

Create a directory, activate a virtual environment, and install Django:

```bash theme={null}
mkdir djangotutorial && cd djangotutorial
python -m venv venv
source venv/bin/activate
pip install Django
```

> [!NOTE]
>
> Windows
> Replace `source venv/bin/activate` with `venv\Scripts\activate`.

Scaffold the project. The trailing `.` places all files directly in the current directory and avoids a nested folder:

```bash theme={null}
django-admin startproject mysite .
```

Your directory now looks like this:

```
djangotutorial/
├── manage.py
└── mysite/
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
```

Initialize a Git repository and make your first commit:

```bash theme={null}
git init && git branch -M main
git add .
git commit -m "Initial Django project"
```

## 2. Add production dependencies

Upsun Cloud runs your Django app using [Gunicorn](https://gunicorn.org), a production-grade WSGI server.
You also need a database adapter that matches your chosen database.

> [tab] MariaDB
>
> ```bash theme={null}
> pip install gunicorn mysqlclient
> ```

> [tab] PostgreSQL
>
> ```bash theme={null}
> pip install gunicorn psycopg2-binary
> ```

Save the dependency list so Upsun Cloud can install it at build time:

```bash theme={null}
pip freeze > requirements.txt
git add requirements.txt
git commit -m "Add production dependencies"
```

## 3. Set environment variables

Create a `.environment` file at the root of your project.
Upsun Cloud sources this file automatically before starting your app, on every environment (production, preview branches, etc.).

```bash .environment theme={null}
export DJANGO_SETTINGS_MODULE="mysite.settings"
export DJANGO_SECRET_KEY="$PLATFORM_PROJECT_ENTROPY"
export DJANGO_ALLOWED_HOSTS="$(echo "$PLATFORM_ROUTES" | base64 --decode | jq -r 'to_entries[] | select(.value.primary == true) | .key' | sed 's:/*$::' | sed 's|https\?://||')"
```

What each variable does:

* **`DJANGO_SETTINGS_MODULE`** — tells Django which settings file to load.
* **`DJANGO_SECRET_KEY`** — uses `PLATFORM_PROJECT_ENTROPY`, a unique value Upsun Cloud generates per project. The key is never stored in your repository.
* **`DJANGO_ALLOWED_HOSTS`** — dynamically extracts the hostname of the current environment from your project routes, so every preview branch and production environment works automatically.

> [!WARNING]
>
> Commit .environment to Git
> This file contains no secrets — the values are resolved at runtime from Upsun Cloud variables.
> It must be committed so Upsun Cloud can source it on every deploy.

```bash theme={null}
git add .environment
git commit -m "Add Upsun Cloud environment variables"
```

## 4. Update `settings.py`

Open `mysite/settings.py` and make three changes.

**Add `import os`** at the top of the file, after `from pathlib import Path`:

```python mysite/settings.py theme={null}
import os
```

**Replace the `ALLOWED_HOSTS` line.** The default `[]` rejects all requests when `DEBUG = False`.
Use the environment variable set in the previous step:

```python mysite/settings.py theme={null}
ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost').split(',')
```

**Add the Upsun Cloud production block** at the very bottom of the file.
It only activates when `PLATFORM_APPLICATION_NAME` is present — a variable Upsun Cloud injects automatically at runtime.
Your local development settings are untouched.

> [tab] MariaDB
>
> ```python mysite/settings.py theme={null}
> # Upsun Cloud production settings
> if os.getenv('PLATFORM_APPLICATION_NAME') is not None:
>     DEBUG = False
>
>     if os.getenv('PLATFORM_APP_DIR') is not None:
>         STATIC_ROOT = os.path.join(os.getenv('PLATFORM_APP_DIR'), 'static')
>
>     if os.getenv('PLATFORM_PROJECT_ENTROPY') is not None:
>         SECRET_KEY = os.getenv('PLATFORM_PROJECT_ENTROPY')
>
>     if os.getenv('PLATFORM_ENVIRONMENT') is not None:
>         DATABASES = {
>             'default': {
>                 'ENGINE': 'django.db.backends.mysql',
>                 'NAME': os.getenv('DATABASE_PATH'),
>                 'USER': os.getenv('DATABASE_USERNAME'),
>                 'PASSWORD': os.getenv('DATABASE_PASSWORD'),
>                 'HOST': os.getenv('DATABASE_HOST'),
>                 'PORT': os.getenv('DATABASE_PORT'),
>             }
>         }
> ```

> [tab] PostgreSQL
>
> ```python mysite/settings.py theme={null}
> # Upsun Cloud production settings
> if os.getenv('PLATFORM_APPLICATION_NAME') is not None:
>     DEBUG = False
>
>     if os.getenv('PLATFORM_APP_DIR') is not None:
>         STATIC_ROOT = os.path.join(os.getenv('PLATFORM_APP_DIR'), 'static')
>
>     if os.getenv('PLATFORM_PROJECT_ENTROPY') is not None:
>         SECRET_KEY = os.getenv('PLATFORM_PROJECT_ENTROPY')
>
>     if os.getenv('PLATFORM_ENVIRONMENT') is not None:
>         DATABASES = {
>             'default': {
>                 'ENGINE': 'django.db.backends.postgresql',
>                 'NAME': os.getenv('DATABASE_PATH'),
>                 'USER': os.getenv('DATABASE_USERNAME'),
>                 'PASSWORD': os.getenv('DATABASE_PASSWORD'),
>                 'HOST': os.getenv('DATABASE_HOST'),
>                 'PORT': os.getenv('DATABASE_PORT'),
>             }
>         }
> ```

The `DATABASE_*` environment variables are exposed automatically by the relationship you define in the next step.

```bash theme={null}
git add mysite/settings.py
git commit -m "Add Upsun Cloud production settings"
```

## 5. Create your Upsun Cloud project

If you don't have an Upsun Cloud project yet, create one with the CLI:

```bash theme={null}
upsun project:create
```

The CLI asks for a project name and region, then automatically links your local repository to the new project.

> [!NOTE]
>
> Already have a project?
> If you created a project from the [Upsun Cloud Console](https://console.upsun.com), link your local repository to it:
>
> ```bash theme={null}
> upsun project:set-remote <PROJECT_ID>
> ```
>
> Find your project ID in the Console or by running `upsun project:list`.

## 6. Configure Upsun Cloud

Create a `.upsun/` directory at the root of your project, then add a `config.yaml` file inside it.
This single file defines your application container, database service, and routing.

> [tab] MariaDB
>
> &#x20;       applications:
> &#x20;         mysite:
> &#x20;           type: python:3.14
>
> &#x20;           build:
> &#x20;             flavor: none
>
> &#x20;           relationships:
> &#x20;             database: "db:mysql"
>
> &#x20;           hooks:
> &#x20;             build: |
> &#x20;               set -eux
> &#x20;               pip install --upgrade pip
> &#x20;               pip install -r requirements.txt
> &#x20;             deploy: |
> &#x20;               set -eux
> &#x20;               python manage.py collectstatic --noinput
> &#x20;               python manage.py migrate
>
> &#x20;           mounts:
> &#x20;             static:
> &#x20;               source: storage
> &#x20;               source\_path: static
>
> &#x20;           web:
> &#x20;             commands:
> &#x20;               start: "gunicorn mysite.wsgi:application -b unix:\$SOCKET"
> &#x20;             upstream:
> &#x20;               socket\_family: unix
> &#x20;             locations:
> &#x20;               /:
> &#x20;                 passthru: true
> &#x20;               /static:
> &#x20;                 allow: true
> &#x20;                 expires: 1h
> &#x20;                 root: static
>
> &#x20;       routes:
> &#x20;         https\://\{default}/:
> &#x20;           type: upstream
> &#x20;           upstream: "mysite:http"
>
> &#x20;       services:
> &#x20;         db:
> &#x20;           type: mariadb:12.3
> &#x20;    &#x20;

> [tab] PostgreSQL
>
> &#x20;       applications:
> &#x20;         mysite:
> &#x20;           type: python:3.14
>
> &#x20;           build:
> &#x20;             flavor: none
>
> &#x20;           relationships:
> &#x20;             database: "db:postgresql"
>
> &#x20;           hooks:
> &#x20;             build: |
> &#x20;               set -eux
> &#x20;               pip install --upgrade pip
> &#x20;               pip install -r requirements.txt
> &#x20;             deploy: |
> &#x20;               set -eux
> &#x20;               python manage.py collectstatic --noinput
> &#x20;               python manage.py migrate
>
> &#x20;           mounts:
> &#x20;             static:
> &#x20;               source: storage
> &#x20;               source\_path: static
>
> &#x20;           web:
> &#x20;             commands:
> &#x20;               start: "gunicorn mysite.wsgi:application -b unix:\$SOCKET"
> &#x20;             upstream:
> &#x20;               socket\_family: unix
> &#x20;             locations:
> &#x20;               /:
> &#x20;                 passthru: true
> &#x20;               /static:
> &#x20;                 allow: true
> &#x20;                 expires: 1h
> &#x20;                 root: static
>
> &#x20;       routes:
> &#x20;         https\://\{default}/:
> &#x20;           type: upstream
> &#x20;           upstream: "mysite:http"
>
> &#x20;       services:
> &#x20;         db:
> &#x20;           type: postgresql:17
> &#x20;    &#x20;

Key points:

* **`build: flavor: none`** — disables default build behaviors so only your `hooks.build` commands run.
* **`relationships`** — exposes `DATABASE_*` environment variables to your app (used by `settings.py`).
* **`hooks.build`** — installs Python dependencies during the build phase (no database access yet).
* **`hooks.deploy`** — runs `collectstatic` and migrations at deploy time (database is available).
* **`mounts`** — declares `static/` as a persistent writable directory for collected static files.
* **`web.commands.start`** — starts Gunicorn over a Unix socket (`$SOCKET`), which Upsun Cloud manages automatically.

```bash theme={null}
git add .upsun/
git commit -m "Add Upsun Cloud configuration"
```

## 7. Deploy

Push everything to Upsun Cloud:

```bash theme={null}
upsun push
```

The first deploy installs dependencies, collects static files, and runs database migrations.
It takes a minute or two. Subsequent deploys are faster.

Open your app once the deploy completes:

```bash theme={null}
upsun url --primary
```

## Troubleshooting

**400 Bad Request on every page**

`ALLOWED_HOSTS` is rejecting the request. Check two things:

1. `mysite/settings.py` has `ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost').split(',')` at the **top level** — not inside the `if` block.
2. `.environment` is committed to Git.

**Build fails — `mysqlclient` or `psycopg2` not found**

`requirements.txt` is missing the database adapter. Run:

```bash theme={null}
pip install gunicorn mysqlclient   # or psycopg2-binary
pip freeze > requirements.txt
git add requirements.txt && git commit -m "Fix missing DB adapter"
upsun push
```

**`gunicorn: command not found` in deploy logs**

Same cause — `gunicorn` is missing from `requirements.txt`. Apply the same fix above.

**Static files return 404**

Check that `STATIC_ROOT` in `settings.py` uses `os.path.join(os.getenv('PLATFORM_APP_DIR'), 'static')` and that the mount key in `.upsun/config.yaml` is also `static`. They must match exactly.

## Further resources

### Documentation

* [Python documentation](https://developer.upsun.com/docs/languages/python)
* [Managing Python dependencies](https://developer.upsun.com/docs/languages/python/dependencies.md)
* [Configuring web servers](https://developer.upsun.com/docs/languages/python/server.md)
* [Getting started guide](https://developer.upsun.com/docs/get-started/here)

### Blogs

* [*Up(sun) and running with Django*](https://upsun.com/blog/setting-up-django-on-upsun/)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.