What you will deploy
You will build a Django Task app that saves tasks to SQLite locally, prepare it for Gunicorn and PostgreSQL, and deploy it with Sealos Skills. You will finish with a public page, a WhiteNoise-served CSS asset, and a Task created and read through the live application.
| Requirement | Version used by this guide |
|---|---|
| Python | 3.12-3.14 |
| Django | >=5.2,<5.3 |
| uv | 0.10 or newer |
| Dependency lock | uv.lock |
Prerequisites:
- Python 3.12, 3.13, or 3.14 available to
uv - uv 0.10+
- Git
- A Sealos Cloud account and target workspace
- Local Codex with access to the project directory
Already have a Django project that works locally? Jump to
Prepare Django for production. Keep your
current dependency manifest and lock, reviewed WSGI or ASGI production server
and entry point, and STORAGES['default']. Replace config in commands with
your project package, merge the WhiteNoise staticfiles storage alias into
your settings, and use an equivalent create/read flow from your application.
Create a Django Task app
Create the project from an empty directory
Working directory: The parent directory that will contain the project.
The Python and Django ranges keep the project on Django 5.2 LTS while allowing
maintained Python releases supported by this guide. uv add writes both
pyproject.toml and uv.lock.
Expected: the repository root contains manage.py, pyproject.toml,
uv.lock, config/, and tasks/.
Register the Task app
File: config/settings.py
Add tasks.apps.TasksConfig as the first item in the generated
INSTALLED_APPS list:
This lets Django discover the Task model and its migrations.
Check it from the repository root:
Expected:
Create the Task model
File: tasks/models.py
Replace the generated file with:
The model gives local and production verification the same database-backed record.
Create the form
File: tasks/forms.py
Create the file with:
ModelForm validates the title against the model before saving it.
Add the create/list view
File: tasks/views.py
Replace the generated file with:
The redirect after a valid POST prevents a browser refresh from submitting the same Task twice.
Route the application
File: tasks/urls.py
Create the file with:
File: config/urls.py
Replace the generated file with:
The project now serves the Task create/list flow at /.
Add the HTML page
File: tasks/templates/tasks/task_list.html
Create the directories from the repository root:
Then create the file with this complete template:
The {% csrf_token %} tag protects the POST request, and the list reads Tasks
through the view's database query.
Add the CSS asset
File: tasks/static/tasks/app.css
Create the directories from the repository root:
Then create the file with:
This file provides a visible static-asset check for both local and production runs.
Create the schema and verify SQLite locally
Working directory: Repository root.
Expected migration and check output includes:
Open http://127.0.0.1:8000/, enter
Ship Django on Sealos, and select Add task. The browser redirects to /
and the Task appears under Task list.
Stop the development server, then confirm the value came from SQLite:
Expected:
The same migration and form flow will run against private PostgreSQL after deployment.
Prepare Django for production
Existing-project readers join here. The sequence follows Django's deployment checklist: declare production dependencies, move configuration into environment variables, collect static files, apply migrations, and start a production server.
Add the production dependencies
Working directory: Repository root.
Gunicorn runs Django's WSGI application, psycopg connects Django to PostgreSQL,
WhiteNoise serves versioned static assets, and django-environ reads typed
settings. uv add updates uv.lock; commit that lock file with the application
so every build installs the resolved versions.
Expected: both checks exit with status 0; a silent uv lock --check indicates
success.
Create safe environment files
File: .gitignore
Create the file with:
The local database, collected static output, virtual environment, and local environment files stay outside source control. The committed example documents the production contract.
File: .env.example
Generate a local secret from the repository root:
Copy the printed value into SECRET_KEY in a new local file:
File: .env
Leave DATABASE_URL unset locally so Django continues to use SQLite. Process
environment variables take precedence over .env during deployment.
Check both files:
Expected:
If you see
django.core.exceptions.ImproperlyConfigured: Set the SECRET_KEY environment variable: Generate the secret, replace<paste-generated-value-here>in.env, and run the command again.
Configure Django for local and production environments
File: config/settings.py
For the project created in this guide, replace the file with the complete
settings below. In an existing project, merge the environment, database,
middleware, static-file, proxy, and security settings while retaining your
project-specific applications, template configuration, reviewed WSGI or ASGI
entry point, and STORAGES['default']. Add the staticfiles storage alias to
the current STORAGES mapping.
BASE_DIR exists before .env is loaded. The DATABASE_URL fallback keeps
SQLite for local development, while the deployment value selects private
PostgreSQL. WhiteNoise's storage backend hashes and compresses collected static
files. The proxy header lets Django recognize the original HTTPS request before
applying redirects and secure cookies.
Run Django's standard check:
Expected:
Run Django's deployment checks with safe production-like values:
Expected: exit status 0. Django can still report HSTS policy warnings such as
security.W004, security.W005, and security.W021. Enable HSTS only after
the domain-wide HTTPS policy is ready to enforce for every subdomain.
Optional Sealos Skills help for readers who already have the plugin installed:
Review the proposed diff before accepting any file change. The primary deployment step remains after the readiness checklist.
Define and test the deployment lifecycle
Use these commands from the repository root:
First confirm that every model change has a migration file:
Expected: No changes detected and exit status 0.
| Stage | Command | Production reason |
|---|---|---|
| Build | uv sync --frozen --no-dev && DEBUG=False SECRET_KEY=build-only-collectstatic-key uv run --frozen --no-dev python manage.py collectstatic --noinput | Installs the locked production dependencies and creates WhiteNoise's static bundle with a harmless build-only key; the protected runtime secret remains a runtime setting. |
| Pre-deploy | uv run --frozen --no-dev python manage.py migrate --noinput | Brings the PostgreSQL schema up to the application revision before web traffic reaches the new process. |
| Start | uv run --frozen --no-dev gunicorn config.wsgi:application --bind 0.0.0.0:$PORT | Runs Django through Gunicorn on the platform-provided network interface and port. |
Run the build command locally:
Expected: collectstatic reports copied and post-processed static files, then
the explicit asset check prints:
Run the pre-deploy command against the local SQLite database:
Expected after the earlier migration:
Start Gunicorn on a temporary local port:
This starts the production server with Django's debug mode disabled.
SECURE_SSL_REDIRECT=False keeps the localhost HTTP check reachable while the
deployed application uses HTTPS.
Expected log line:
In a second terminal, verify the page and collected CSS:
Expected:
Stop Gunicorn with Ctrl+C.
Check the storage boundary
WhiteNoise serves application-owned static assets from STATIC_ROOT. Durable
user uploads need an object-storage integration.
Search the repository for upload fields or media settings:
Expected for this Task app: an empty result. When an existing project returns a match, complete the object-storage guide before deployment.
Save the ready repository
Working directory: Repository root.
The commit gives the deployment a stable source revision and includes
pyproject.toml, uv.lock, the migration, application code, and safe
.env.example. Expected: the commit succeeds, and git status --short returns
an empty status.
Production Readiness Checklist
- Python, Django, and uv match the stated version requirements.
-
uv.lockis committed, anduv sync --frozen --no-devsucceeds. - The local SQLite create/read flow returns
Ship Django on Sealos. -
uv run python manage.py checkreports zero issues. -
makemigrations --checkreportsNo changes detected. -
check --deploy --fail-level ERRORexits with status0after the HSTS policy warnings are reviewed. -
collectstaticsucceeds, andstaticfiles/tasks/app.cssexists. -
migrate --noinputsucceeds. - Gunicorn runs with
DEBUG=False, binds to0.0.0.0:$PORT, and serves the page and CSS. -
.env.examplecontains placeholders and exact non-sensitive values. -
.envis ignored. - Private PostgreSQL is listed with
DATABASE_URL. - Every detected upload field has a durable object-storage path.
Deploy with Sealos Skills
Install the current Sealos Skills plugin in local Codex:
Open the repository root in local Codex and send this request:
Review the proposed plan before confirming it:
- The selected Sealos account and workspace are correct.
- The source is this repository and the intended revision.
- Build, pre-deploy, start, and port match the commands above.
- PostgreSQL is private and supplies
DATABASE_URLto the Django application. SECRET_KEYis protected; the remaining variable names and values match the prompt.- The public route forwards to port
8080.
Confirm the plan in local Codex and complete any Sealos authentication or workspace prompt there. After deployment, use the Sealos web interface to review protected-variable status, database details, logs, resources, public access, and the returned domain.
Verify the live Django application
Check build, migration, and start results
Use the Sealos Skills result and the Sealos web interface to inspect the completed deployment. Confirm these results in order:
- The build completed its frozen install and printed the
collectstaticcopied/post-processed summary. - The pre-deploy result contains
Applying tasks.0001_initial... OKon a new PostgreSQL database, orNo migrations to apply.on a repeated deployment. - The application log contains a Gunicorn
Listening at:line for0.0.0.0:8080and contains no active Django, Gunicorn, migration, or database failure. - The Sealos Project Canvas shows the Django application, private PostgreSQL, and Public Access in a healthy state.
Sealos Project Canvas showing the healthy Django application, private PostgreSQL, and public accessIf you see a migration connection or authentication error: Review the application variables and private PostgreSQL connection status in the Sealos web interface, correct
DATABASE_URLthrough local Codex with Sealos Skills, and deploy the saved target again.
Open the page and its static asset
Open the HTTPS URL returned by Sealos. The page should show Django tasks and Task list.
Set the returned host locally, then request the CSS directly:
Expected:
If you see
DisallowedHostor a CSRF origin error: SetALLOWED_HOSTSto the exact hostname andCSRF_TRUSTED_ORIGINSto its fullhttps://origin through local Codex with Sealos Skills, then deploy the saved target again.
Create and read a Task through Django
- Enter
Runtime proof from Sealosin the Title field. - Select Add task.
- Confirm the browser returns to
/and Task list showsRuntime proof from Sealos. - Refresh the page and confirm the same Task remains visible.
Live Django application on Sealos HTTPS showing the saved Runtime proof from Sealos taskThe POST exercises Django's CSRF-protected ModelForm; the redirected GET reads
the saved row from private PostgreSQL.
Deploy your next change and continue
Change the visible heading in tasks/templates/tasks/task_list.html:
From the repository root, ask Sealos Skills to update the saved application:
Review the plan and confirm that it targets the saved application. After the update completes:
- Refresh the original URL and confirm Django tasks on Sealos appears.
- Submit a second Task named
Verify the update. - Confirm both
Verify the updateandRuntime proof from Sealosappear under Task list after the redirect and another refresh. - Request
/static/tasks/app.cssagain and confirm the CSS still loads.