Skip to main content
Django apps deploy on Suga straight from a GitHub repo, with Postgres running alongside as a private container. Suga detects the Python install and start commands automatically, so no Dockerfile is needed. This guide explains how to deploy a Django app with Postgres in two ways:
  1. Step-by-step via the canvas
  2. Using a coding agent
Your repo needs a manage.py, your project’s WSGI entry at <project>/wsgi.py, and a requirements.txt (or pyproject.toml) listing django, gunicorn, and a Postgres driver (psycopg or psycopg2). The guide adds dj-database-url so settings.py can parse DATABASE_URL, and whitenoise so the app can serve static files without a separate nginx; both are optional if you already handle those pieces differently. settings.py should read SECRET_KEY, DATABASE_URL, ALLOWED_HOSTS, and DEBUG from environment variables so the same code runs locally and on Suga. WhiteNoise middleware belongs right after SecurityMiddleware, and STATIC_ROOT should be set.
Env vars only reach the running container, not the build, and collectstatic imports settings.py during the build. Read them with fallbacks so the import still works: os.environ.get("SECRET_KEY", "insecure-build-key") and dj_database_url.config(default="sqlite://:memory:").

Deploy using the Canvas

1

Create a project from your repo

In the Suga dashboard, click New project and pick your Django repo from the GitHub list, installing the Suga GitHub App if prompted. On the import page, name the project, set the branch you want deployed, and leave the build method on auto-detect; Suga detects the Python install and start commands. See Import from a GitHub repository for the full set of import options.Click Create project. Suga opens the canvas with the app service already connected to your repo.
2

Add Postgres from a template

Right-click empty space on the canvas, choose Add → Template, and pick PostgreSQL. Suga prompts for POSTGRES_USER and POSTGRES_DB (both default to postgres) and pre-fills a generated POSTGRES_PASSWORD.
Copy the password now, it won’t be visible after creation.
Suga adds the postgres container with port 5432 private and a volume mounted at /var/lib/postgresql/data.
3

Expose the app on HTTPS

Select the app service. On its Config tab, in the Private Network section, set the port to 8000. Then, in the Public Network section, click Add Endpoint → HTTPS Domain and choose port 8000.
4

Wire the app's environment variables

On the app container, add a DATABASE_URL env var. Set the value to a connection string with an embedded reference to Postgres’s password:
The {{...}} picker inserts the reference alongside literal text, so the app reads the password from postgres at deploy time without duplicating it. Swap the user and database name if you changed them from the defaults.Then add ALLOWED_HOSTS: choose Reference, and pick the app container → SUGA_PUBLIC_HOSTNAME. Django reads it at boot to accept requests from your public URL.Last, add DEBUG set to False. A fresh settings.py defaults to True, which serves tracebacks and settings to anyone who hits the public URL.
Suga canvas showing postgres and app containers wired together with the postgres data volume attached and the app container's build repo visible in the properties panel
5

Configure build args and apply

On the app container, add two build args:
  • RAILPACK_BUILD_CMD: python manage.py collectstatic --noinput, which bakes WhiteNoise’s static files into the image at build time.
  • RAILPACK_START_CMD: python manage.py migrate && gunicorn <project>.wsgi:application --bind 0.0.0.0:8000, swapping <project> for the folder next to manage.py that holds wsgi.py. Runs pending migrations before Gunicorn boots.
Then add SECRET_KEY as a Sensitive env var on the app container (generate one with python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"), and click Apply in the top right.Suga clones the app repo, installs dependencies, collects static files during the build, applies migrations at boot, starts Gunicorn on port 8000, and rolls out both containers. The Django app is served at the public URL and reaches Postgres privately at postgres:5432. Open /admin and you’ll get the Django admin login with its stylesheets in place, which confirms both the deploy and the static files.

Deploy using an Agent

To deploy with a coding agent such as Claude Code, Codex, or OpenCode, follow these steps:
1

Connect the Suga MCP

If you haven’t already, connect your agent to the Suga MCP server.
2

Ask your agent to deploy

Ask your agent to deploy your repo to Suga. Here’s a basic prompt you can use, with your repo, branch, and Django project name filled in:
Prompt
The agent will give you a link to the new environment on the Suga canvas. Open it to review the setup.
3

Set the sensitive values

Set POSTGRES_PASSWORD on the postgres container: click the value input and paste a value generated with openssl rand -hex 32. On the app container, add SECRET_KEY as a Sensitive env var (generate one with python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())").
Save both values somewhere secure, you’ll want them later.
4

Apply to deploy

Click Apply in the top right. Suga clones the app repo, installs dependencies, runs migrations, starts Gunicorn on port 8000, and rolls out both containers. The Django app is served at the public URL and reaches Postgres privately at postgres:5432. Open /admin and you’ll get the Django admin login with its stylesheets in place, which confirms both the deploy and the static files.

FAQ

No. Suga detects Python from requirements.txt or pyproject.toml and runs the standard install and start flow. If you already have a Dockerfile, you can tell the agent to use it instead; otherwise auto-detect handles the common case.
Python 3.10 and up, defaulting to 3.13. Pin a different one in .python-version, runtime.txt, .tool-versions, or mise.toml.
Pass RAILPACK_START_CMD as a build arg on the app container with your exact command. Common alternatives: gunicorn <project>.wsgi:application --bind 0.0.0.0:8000 for a custom project name, daphne -b 0.0.0.0 -p 8000 <project>.asgi:application for ASGI or Channels, uwsgi --http :8000 --module <project>.wsgi:application for uWSGI, or hypercorn <project>.asgi:application --bind 0.0.0.0:8000 for Hypercorn. Suga only cares that something listens on the container’s private port.
Two options. If your project uses django-environ, it parses DATABASE_URL the same way, so keep the guide’s env var and drop dj-database-url from requirements.txt. If your settings.py reads individual DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, and DB_NAME variables instead, skip DATABASE_URL on the app container and set those variables directly, referencing the postgres container’s POSTGRES_PASSWORD for the password. The reference works for any variable name.
Wrap the start command with the migration: pass RAILPACK_START_CMD=python manage.py migrate && gunicorn <project>.wsgi:application --bind 0.0.0.0:8000 as a build arg. Migrations run each time a new container boots and no-op if there’s nothing to apply.
Yes. Drop the python manage.py migrate && prefix from RAILPACK_START_CMD and run migrations manually — from the container’s shell in the dashboard, or from your CI pipeline before you Apply. Running migrations at boot is convenient, but teams with strict migration workflows often prefer to trigger them explicitly.
Add RAILPACK_BUILD_CMD=python manage.py collectstatic --noinput as a build arg on the app container. In settings.py, add WhiteNoise middleware to MIDDLEWARE (right after SecurityMiddleware) and set STATIC_ROOT. WhiteNoise then serves the baked static files without a separate nginx.
Skip WhiteNoise entirely. Point STATICFILES_STORAGE at django-storages’ S3 backend (or your CDN’s storage backend) in settings.py, and add AWS credentials as Sensitive env vars on the app container. Those only reach the running container, so collectstatic can’t upload during the build: drop RAILPACK_BUILD_CMD and run collectstatic from CI before you Apply.
Yes if you use Django’s admin, which ships with its own CSS and JS. WhiteNoise’s CompressedManifestStaticFilesStorage refuses to serve any static asset unless the manifest was built. If your project is a pure JSON API with the admin disabled, drop RAILPACK_BUILD_CMD from the build args and remove WhiteNoise from MIDDLEWARE.
Env vars only reach the running container, so settings.py can’t read them during the build. Give every setting read at import time a fallback, for example os.environ.get("SECRET_KEY", "insecure-build-key") instead of os.environ["SECRET_KEY"]. The real values arrive as env vars at boot.
Yes. Swap the postgres:18-alpine container for a MySQL, MariaDB, or MongoDB image (or any container that runs the database you want). Update Django’s DATABASES setting and the driver in your requirements.txt to match.
Add a second app container on the canvas with the same repo but a different start command, for example RAILPACK_START_CMD=celery -A <project> worker --loglevel=info. Set the same DATABASE_URL and any broker env vars, and leave it with no public endpoint. Add a Redis container from the template if Celery needs a broker.
The managed volume persists across restarts, redeploys, and rollbacks. For point-in-time recovery or off-site backups, run pg_dump on a schedule from another container, or point the app at a managed Postgres service instead of a container.
Yes. Attach your domain to the app container in the dashboard once the app is deployed. Add the new hostname to your Django ALLOWED_HOSTS too.
The Free tier fits a small Django plus Postgres stack, including the build history and volume. Pro is per-seat with hosting credits that offset compute and storage. Full pricing at suga.app/pricing.