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

# Build a Django app on floo

> End-to-end guide. Deploy a Django app, add Postgres, add per-user auth, and ship a custom domain — all from a real Django project.

This guide walks a Django 4+ app from local code to a production URL with a database and per-user auth. Every step has runnable Python code. By the end you have a working app at `https://<app>.on.getfloo.com` with a Postgres sibling service, signed-in users, and (optionally) your own domain.

If you've never deployed to floo before, read [Golden Path](/guides/golden-path) first for the minimal three-command flow.

## Before you start

You need:

* A Django 4+ project (or a fresh `django-admin startproject mysite`).
* The project pushed to a GitHub repository.
* The floo CLI installed and authenticated (`curl -fsSL https://getfloo.com/install.sh | bash` then `floo auth login`).

## 1. Add a Dockerfile

```dockerfile Dockerfile theme={null}
FROM python:3.12-slim

ENV PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends build-essential libpq-dev && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY . .

RUN python manage.py collectstatic --noinput

EXPOSE 8000
CMD ["gunicorn", "mysite.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]
```

Add `gunicorn` and `dj-database-url` to your `requirements.txt`:

```
Django>=4.2
gunicorn>=21
dj-database-url>=2.1
psycopg[binary]>=3
whitenoise>=6.6
requests>=2.31
```

<Tip>
  Bind to `0.0.0.0`, not `127.0.0.1`. Cloud Run only routes traffic to processes bound to all interfaces.
</Tip>

## 2. Configure Django for production

Update `mysite/settings.py` to read floo's runtime env vars:

```python mysite/settings.py theme={null}
import os
from pathlib import Path
import dj_database_url

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "dev-only-do-not-use-in-prod")
DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"

# floo's edge sets X-Forwarded-Host. Trust it for ALLOWED_HOSTS.
ALLOWED_HOSTS = ["*"] if DEBUG else [
    ".on.getfloo.com",
    *os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(","),
]

# Trust X-Forwarded-Proto so Django knows the request is HTTPS behind floo's edge.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True

# Cookies
SESSION_COOKIE_SECURE = not DEBUG
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SECURE = not DEBUG

# WhiteNoise serves collectstatic output
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ... rest of your middleware
]

STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
    "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
    "staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}

# Database — read DATABASE_URL from floo
DATABASES = {
    "default": dj_database_url.config(default="sqlite:///db.sqlite3", conn_max_age=600),
}
```

Generate a real `DJANGO_SECRET_KEY` and set it once the app exists (step 4).

## 3. Initialize the floo config

```bash theme={null}
floo init my-django-app
```

```toml floo.app.toml theme={null}
[app]
name = "my-django-app"

[services.web]
type = "web"
path = "."
port = 8000
ingress = "public"
dev_command = "python manage.py runserver 0.0.0.0:8000"
migrate_command = "python manage.py migrate --noinput"
```

`migrate_command` runs after every deploy and promote.

## 4. Connect the repo and deploy

```bash theme={null}
git add . && git commit -m "feat: floo config + Dockerfile"
git push origin main
floo apps github connect owner/my-django-app
floo deploys watch --app my-django-app
```

Set the secret key once the app exists, then redeploy:

```bash theme={null}
floo env set DJANGO_SECRET_KEY="$(python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())')" --app my-django-app
floo redeploy --app my-django-app
```

```bash theme={null}
floo apps status my-django-app
```

<Check>
  **Your Django app is live** at `https://my-django-app-dev.on.getfloo.com`.
</Check>

## 5. Add a Postgres database

```bash theme={null}
floo services add postgres --app my-django-app
git add .floo/services.lock && git commit -m "feat: add postgres"
git push origin main
```

`DATABASE_URL` is injected. `dj-database-url` parses it automatically — no settings change needed. The next deploy runs your `migrate_command` against the new database before traffic shifts.

## 6. Add per-user auth

floo manages user authentication for you. Set `access_mode = "accounts"` in `floo.app.toml`:

```toml floo.app.toml theme={null}
[app]
name = "my-django-app"
access_mode = "accounts"
```

Push and deploy. From the next deploy onward, floo's gateway sits in front of your app and:

* Redirects unauthenticated requests to a hosted login page.
* Validates the session cookie on every request.
* Injects identity headers into every request that reaches your Django app.

Add a tiny middleware that hangs the floo user on `request.floo_user`:

```python mysite/middleware.py theme={null}
from dataclasses import dataclass

@dataclass
class FlooUser:
    id: str
    email: str
    name: str | None

class FlooUserMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        email = request.META.get("HTTP_X_FLOO_USER_EMAIL")
        user_id = request.META.get("HTTP_X_FLOO_USER_ID")
        name = request.META.get("HTTP_X_FLOO_USER_NAME")
        request.floo_user = FlooUser(id=user_id, email=email, name=name) if email else None
        return self.get_response(request)
```

Wire it up:

```python mysite/settings.py theme={null}
MIDDLEWARE = [
    # ... existing entries
    "mysite.middleware.FlooUserMiddleware",
]
```

Use it in views:

```python mysite/views.py theme={null}
from django.http import JsonResponse, HttpResponseUnauthorized

def dashboard(request):
    if not request.floo_user:
        return HttpResponseUnauthorized()
    return JsonResponse({"hello": request.floo_user.email})
```

For local development, send the headers yourself or extend the middleware to inject a fixture user when they're missing.

For the full reference on access modes and identity headers, see [Add User Auth to Your App](/guides/app-auth).

## 7. Add a custom domain

```bash theme={null}
floo domains add app.example.com --app my-django-app
floo env set DJANGO_ALLOWED_HOSTS=app.example.com --app my-django-app
floo redeploy --app my-django-app
```

`USE_X_FORWARDED_HOST = True` and `SECURE_PROXY_SSL_HEADER` mean Django builds correct absolute URLs (for redirects, mailers, `request.build_absolute_uri`) without extra config.

## 8. Local development with prod data

```bash theme={null}
floo dev --app my-django-app --service web
```

Runs `dev_command` locally with `DATABASE_URL` and other env vars sourced from your dev floo app — real Cloud SQL connection, no credentials in your shell history.

To also test signed-in flows for this accounts-mode app, add `--fixture-user`:

```bash theme={null}
floo dev --app my-django-app --service web --fixture-user you@example.com
```

`floo dev` then starts a small proxy in front of each service that injects the same `X-Floo-User-*` headers floo's gateway adds in production. The output table shows both the raw service URL and the auth-proxied URL — hit the auth-proxied one for any path that reads identity headers.

## Common gotchas

* **`/healthz` is reserved.** Cloud Run's edge intercepts that exact path. Use `/health` or `/livez`.
* **Bind to `0.0.0.0`.** `127.0.0.1` won't accept Cloud Run traffic.
* **`DEBUG = True` in prod is a security hole.** Set `DJANGO_DEBUG=false` (or just don't set it — the default in the example above).
* **`DJANGO_SECRET_KEY` must be set.** Without it, Django's session cookies can be forged. Generate with `get_random_secret_key()` and set via `floo env set`.
* **`collectstatic` runs in the Dockerfile.** WhiteNoise's `CompressedManifestStaticFilesStorage` requires it.
* **`SECURE_PROXY_SSL_HEADER` is required behind a proxy.** Without it, `request.is_secure()` returns False and Django redirect loops can form when `SECURE_SSL_REDIRECT=True`.

## What's next

<CardGroup cols={2}>
  <Card title="Add User Auth — full reference" href="/guides/app-auth">
    Identity headers, access policies, and access modes in detail.
  </Card>

  <Card title="Managed Services" href="/guides/managed-services">
    Postgres, Redis, Storage — what they cost and how isolation works.
  </Card>

  <Card title="Custom Domains" href="/guides/custom-domains">
    DNS, verification, multi-service routing.
  </Card>

  <Card title="Cron Jobs" href="/guides/cron-jobs">
    Schedule recurring Django management commands inside your container.
  </Card>
</CardGroup>
