DjangoPlay — Deployment & CI/CD Architecture
DjangoPlay uses GitLab CI/CD as the primary source-control and deployment automation platform.
On this page ▾
- 1. Overview
- 2. Deployment Architecture
- 3.1 GitHub Mirror Architecture
- 4.1 Production Deployment Flow
- 5.1 Connect to Production
- 5.2 Determine the Application Directory
- 5.3 Update the Application
- 5.4 Install Dependencies
- 5.5 Run Database Migrations
- 5.6 Collect Static Files
- 5.7 Restart Application Services
- 11.1 DjangoPlay Web Application
- 11.2 Python Libraries
- Manual production gate
- SSH key-based access
- Known-host verification
- Non-interactive migrations
- Service restart
- Single production host
- Direct SSH deployment
- Single primary database
- Manual production gate
- Limited horizontal scaling
1. Overview
- DjangoPlay web application — GitLab CI → manual production deployment over SSH → production GCP VM.
- Python/PyPI libraries — GitLab CI → test → build → version check → publish to PyPI.
- GitHub is a mirror, not the primary CI/CD source. GitLab triggers GitHub Actions after changes to
main.
Production deployment is intentionally simple:
- GitLab hosts the primary repository.
- GitLab CI/CD controls the deployment pipeline.
- Production deployment is initiated manually from the
mainbranch. - GitLab connects to the production GCP VM over SSH.
- The production server pulls the latest
mainbranch. - Python dependencies are installed.
- Django migrations are applied.
- Static files are collected.
- DjangoPlay and Celery services are restarted.
GitHub is maintained as a repository mirror and is not the primary deployment platform.
2. Deployment Architecture
The current production delivery architecture is:
flowchart LR
DEV["Developer"]
GITLAB["<b>GitLab</b><br/><small>Primary Repository</small>"]
CI["<b>GitLab CI/CD</b><br/><small>Pipeline Orchestration</small>"]
SSH["<b>SSH Deployment</b><br/><small>CI → Production VM</small>"]
VM["<b>GCP Production VM</b>"]
NGINX["Nginx"]
GUNICORN["Gunicorn"]
DJANGO["DjangoPlay"]
CELERY["Celery"]
REDIS["Redis"]
POSTGRES["PostgreSQL"]
AUTHX["AuthX"]
DEV --> GITLAB
GITLAB --> CI
CI --> SSH
SSH --> VM
VM --> NGINX
NGINX --> GUNICORN
GUNICORN --> DJANGO
DJANGO --> POSTGRES
DJANGO --> REDIS
REDIS --> CELERY
DJANGO --> AUTHX
classDef source fill:#fceee8,stroke:#d65a2a,stroke-width:2px,color:#7d321c
classDef ci fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a
classDef infra fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b
classDef app fill:#f5f3ed,stroke:#777,stroke-width:2px,color:#444
classDef auth fill:#f3eafa,stroke:#7b4bb7,stroke-width:2px,color:#542d82
class DEV,GITLAB source
class CI,SSH ci
class VM,NGINX,GUNICORN,DJANGO,CELERY,REDIS,POSTGRES infra
class AUTHX auth
`The deployment path is deliberately direct. There is currently no Kubernetes cluster, container orchestration platform, or cloud-native deployment controller in the production path.
3. Source Control and Repository Strategy
GitLab is the authoritative repository for DjangoPlay.
The normal development flow is:
Developer
│
▼
GitLab
│
▼
main
│
├──────────────► Production Deployment
│
└──────────────► GitHub MirrorThe production deployment pipeline operates from the GitLab repository.
GitHub receives a mirror of the repository for redundancy, visibility, and repository availability.
GitHub is therefore not the source of truth for production deployment.
3.1 GitHub Mirror Architecture
After changes reach main, GitLab triggers a GitHub repository-dispatch event.
flowchart LR
GITLAB["<b>GitLab</b><br/>Primary Repository"]
CI["GitLab CI<br/><small>github-mirror.yml</small>"]
DISPATCH["GitHub<br/><small>repository_dispatch</small>"]
ACTION["GitHub Actions<br/><small>Mirror Workflow</small>"]
GITHUB["GitHub Repository<br/><small>Mirror</small>"]
GITLAB --> CI
CI --> DISPATCH
DISPATCH --> ACTION
ACTION --> GITHUB
classDef primary fill:#fceee8,stroke:#d65a2a,stroke-width:2px,color:#7d321c
classDef ci fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a
classDef mirror fill:#f5f3ed,stroke:#777,stroke-width:2px,color:#444
class GITLAB primary
class CI,DISPATCH,ACTION ci
class GITHUB mirrorThe GitHub workflow performs a mirror operation using:
- GitLab access token
- GitHub access token
- GitLab repository path
The mirror pushes branches and tags and prunes removed branches.
Internal GitLab references are intentionally not mirrored.
4. DjangoPlay Web Application Deployment
The DjangoPlay web application uses a manual production deployment pipeline.
The deployment job runs only for the main branch and is configured as a
manual GitLab CI job.
This provides an explicit deployment gate between a successful merge to
main and production rollout.
4.1 Production Deployment Flow
flowchart TD
MAIN["<b>GitLab main</b><br/><small>Latest application code</small>"]
PIPELINE["GitLab CI/CD"]
MANUAL["<b>Manual Deploy</b><br/><small>deploy_production</small>"]
SSH["SSH<br/><small>Production server</small>"]
UPDATE["Update application<br/><small>checkout + pull main</small>"]
DEPS["Install dependencies<br/><small>pip install -e .[dev]<br/>djangoplay-cli</small>"]
MIGRATE["Django migrations<br/><small>manage.py migrate --noinput</small>"]
STATIC["Collect static files<br/><small>collectstatic --noinput</small>"]
RESTART["Restart services<br/><small>djangoplay + djangoplay-celery</small>"]
LIVE["<b>Production</b><br/>app.djangoplay.org"]
MAIN --> PIPELINE
PIPELINE --> MANUAL
MANUAL --> SSH
SSH --> UPDATE
UPDATE --> DEPS
DEPS --> MIGRATE
MIGRATE --> STATIC
STATIC --> RESTART
RESTART --> LIVE
classDef source fill:#fceee8,stroke:#d65a2a,stroke-width:2px,color:#7d321c
classDef ci fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a
classDef deploy fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b
classDef production fill:#f3eafa,stroke:#7b4bb7,stroke-width:2px,color:#542d82
class MAIN source
class PIPELINE,MANUAL,SSH ci
class UPDATE,DEPS,MIGRATE,STATIC,RESTART deploy
class LIVE production5. Production Deployment Steps
The deployment job performs the following operations.
5.1 Connect to Production
GitLab CI starts an SSH agent and loads the deployment private key.
The production host is obtained from the GitLab CI/CD variable:
SERVER_HOSTThe server's SSH host key is added to the CI runner's known_hosts.
The deployment connection uses the production deployment account and then
uses sudo for operations performed as the djangoplay system user.
5.2 Determine the Application Directory
The production configuration is stored under:
/opt/djangoplay/.dplay/config.yamlThe deployment process reads the configured:
webapp_dirvalue from that configuration.
This is important because the production .dplay configuration is owned by
the djangoplay system user and is not assumed to exist under the SSH
user's home directory.
5.3 Update the Application
The deployment process switches to the configured application directory and updates the production checkout:
git checkout main
git stash
git pull origin mainThe production application therefore follows the GitLab main branch.
5.4 Install Dependencies
The deployment environment activates the application's virtual environment:
.venvThe deployment then installs:
pip install -e '.[dev]'
pip install djangoplay-cliThe editable installation ensures that the deployed Django application uses the checked-out source tree.
5.5 Run Database Migrations
Django migrations are executed automatically during deployment:
python manage.py migrate --noinputDatabase schema changes are therefore applied before the application services are restarted.
5.6 Collect Static Files
Production static files are collected using:
python manage.py collectstatic --noinputThe deployment does not require a separate manual static-file collection step.
5.7 Restart Application Services
The deployment completes by restarting:
djangoplay
djangoplay-celeryThe first service runs the Django web application.
The second service runs the Celery worker.
The deployment therefore updates both synchronous web processing and background task processing together.
6. Production Runtime Architecture
The deployment pipeline ultimately updates the following production runtime:
flowchart TD
CF["<b>Cloudflare</b><br/>DNS / Edge"]
NGINX["<b>Nginx</b><br/><small>Reverse Proxy / TLS</small>"]
GUNICORN["<b>Gunicorn</b><br/><small>WSGI Server</small>"]
DJANGO["<b>DjangoPlay</b><br/><small>Web Application</small>"]
CELERY["<b>Celery Worker</b><br/><small>Background Processing</small>"]
REDIS["<b>Redis</b><br/><small>Cache / Broker</small>"]
POSTGRES["<b>PostgreSQL</b><br/><small>Application Database</small>"]
AUTHX["<b>AuthX</b><br/><small>Identity Service</small>"]
EXTERNAL["<b>External Services</b><br/><small>Email • AI • APIs • R2</small>"]
CF --> NGINX
NGINX --> GUNICORN
GUNICORN --> DJANGO
DJANGO --> POSTGRES
DJANGO --> REDIS
REDIS --> CELERY
DJANGO --> AUTHX
DJANGO --> EXTERNAL
CELERY --> EXTERNAL
classDef edge fill:#fceee8,stroke:#d65a2a,stroke-width:2px,color:#7d321c
classDef app fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a
classDef infra fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b
classDef auth fill:#f3eafa,stroke:#7b4bb7,stroke-width:2px,color:#542d82
classDef external fill:#eeeeff,stroke:#5a55c9,stroke-width:2px,color:#403b91
class CF edge
class NGINX,GUNICORN,DJANGO,CELERY app
class REDIS,POSTGRES infra
class AUTHX auth
class EXTERNAL external7. Production Server Services
The current production VM runs the core DjangoPlay services directly.
| Service | Role |
|---|---|
nginx |
Reverse proxy and TLS termination |
djangoplay |
Django/Gunicorn application service |
djangoplay-celery |
Celery background worker |
| Redis | Cache and Celery broker |
| PostgreSQL | Primary application database |
| AuthX | Identity and authentication service |
The application and worker are restarted together during deployment.
8. Production Configuration
Production configuration is maintained outside the Git repository.
The deployment server uses the DjangoPlay configuration structure under:
/opt/djangoplay/.dplay/The primary configuration includes:
config.yaml
.secrets
.authx
.appdataThe exact configuration keys and their requirement classification are documented separately in the environment configuration reference.
The deployment documentation should therefore describe how configuration participates in deployment, rather than duplicating the complete configuration reference.
9. Deployment Secrets
Deployment credentials are stored as GitLab CI/CD variables rather than being committed to the repository.
The deployment pipeline uses credentials such as:
| Variable | Purpose |
|---|---|
SSH_PRIVATE_KEY |
SSH authentication to production |
SERVER_HOST |
Production server hostname |
GH_TOKEN |
GitHub mirror trigger |
The exact GitLab project/group variable configuration is an operational concern and should be managed through GitLab CI/CD settings.
Private keys, API tokens, passwords, and service credentials must never be committed to source control.
10. Deployment Environment
The current production environment is intentionally lightweight.
Cloudflare
│
▼
GCP e2-micro VM
│
├── Nginx
├── Gunicorn / DjangoPlay
├── Celery
├── Redis
├── PostgreSQL
└── AuthXThe small VM is an intentional operational constraint.
The current deployment architecture prioritizes:
- Low operational overhead
- Simple administration
- Predictable deployment
- Minimal infrastructure
- Direct service management
- Easy recovery and troubleshooting
11. Application Deployment vs Library Release
DjangoPlay web application deployment and Python package releases use different CI/CD flows.
11.1 DjangoPlay Web Application
GitLab main
│
▼
Manual production deployment
│
▼
SSH → GCP VM
│
▼
Django + Celery restart11.2 Python Libraries
Python libraries use a package-release pipeline:
GitLab main
│
▼
Test
│
▼
Build
│
▼
Version Check
│
▼
Publish
│
▼
PyPIThis separation is intentional.
A library release publishes a Python package, while the DjangoPlay web application deployment updates a running production system.
12. PyPI Release Architecture
The standard Python library pipeline consists of four primary stages:
- Test
- Build
- Version Check
- Publish
The pipeline is reused across Python libraries.
flowchart LR
SOURCE["<b>GitLab</b><br/><small>main branch</small>"]
TEST["<b>Test</b><br/><small>Ruff + Pytest</small>"]
BUILD["<b>Build</b><br/><small>Python package artifacts</small>"]
VERSION["<b>Version Check</b><br/><small>Semver + PyPI comparison</small>"]
PUBLISH["<b>Publish</b><br/><small>Twine</small>"]
PYPI["<b>PyPI</b><br/><small>Published package</small>"]
SOURCE --> TEST
TEST --> BUILD
BUILD --> VERSION
VERSION --> PUBLISH
PUBLISH --> PYPI
classDef source fill:#fceee8,stroke:#d65a2a,stroke-width:2px,color:#7d321c
classDef pipeline fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a
classDef publish fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b
class SOURCE source
class TEST,BUILD,VERSION,PUBLISH pipeline
class PYPI publish13. PyPI Test Stage
The test stage uses a Python 3.11 CI image.
It performs:
pip install -e ".[dev]"
pip install pytest ruffand then executes:
ruff check .
pytest --tb=shortThe current shared library pipeline treats these checks as non-blocking by using:
ruff check . || true
pytest --tb=short || trueThe stage therefore provides automated validation feedback without currently blocking the subsequent build stage on lint or test failures.
This behavior is intentional in the current pipeline and should be revisited if the project adopts strict CI quality gates.
14. PyPI Build Stage
The build stage creates standard Python distribution artifacts:
python -m buildThe generated artifacts are stored under:
dist/and passed to subsequent jobs as CI artifacts.
Typical outputs include:
*.whl
*.tar.gz15. PyPI Version Check
Before publishing, the pipeline:
- Reads the version from
pyproject.toml. - Validates the version format.
- Queries PyPI for the currently published version.
- Prevents publishing when the same version already exists.
The purpose is to prevent accidental republishing of an already-published package version.
If PyPI cannot be reached, the current implementation proceeds rather than blocking the release.
16. PyPI Publishing
The publish job uses Twine:
twine upload dist/* --non-interactiveAuthentication is supplied through GitLab CI/CD variables:
PYPI_USERNAME
PYPI_PASSWORDThe conventional PyPI token username is:
__token__The PyPI API token is stored as the password value.
17. PyPI Pipeline Trigger Rules
The standard library pipeline supports:
- Push to
main - Manual web-triggered pipelines
- API-triggered pipelines
- Merge request pipelines
Publishing itself is prevented for merge request pipelines.
The publish stage runs automatically after the required preceding stages
for main.
This provides a distinction between:
Pipeline execution
│
├── Merge Request → validation
│
└── main → release eligible18. GitLab CI Configuration Structure
The CI configuration is intentionally split into reusable files.
For the DjangoPlay web application:
.gitlab/
└── ci/
├── github-mirror.yml
└── deploy.yml
.gitlab-ci.ymlThe root pipeline includes the individual CI definitions:
include:
- local: '.gitlab/ci/github-mirror.yml'
- local: '.gitlab/ci/deploy.yml'For Python libraries, the common structure is:
.gitlab/
└── ci/
├── pypi-release.yml
└── github-mirror.yml
.gitlab-ci.ymlThe root CI file defines the stages and includes the reusable pipeline components.
19. Deployment Safety Characteristics
The current deployment process includes several operational safeguards.
Manual production gate
Production deployment is explicitly manual:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manualA merge to main therefore does not automatically restart production.
SSH key-based access
Production deployment uses an SSH private key stored as a protected CI/CD variable.
Known-host verification
The deployment runner records the production host key before connecting.
Non-interactive migrations
Database migrations run with:
--noinputService restart
The Django and Celery services are restarted together so that application code and asynchronous workers are updated consistently.
20. Current Deployment Limitations
The current architecture intentionally remains simple, but it has known limitations.
Single production host
The production runtime currently uses a single GCP VM.
Failure of the VM therefore affects the application.
Direct SSH deployment
Deployment is performed through SSH rather than a container orchestration platform or managed deployment service.
Single primary database
PostgreSQL is currently the primary application database rather than a multi-node database cluster.
Manual production gate
Production deployment requires manual approval.
Limited horizontal scaling
The current deployment model is optimized for simplicity rather than multi-instance horizontal scaling.
These limitations are architectural constraints of the current deployment, not accidental requirements.
21. Future Deployment Evolution
As DjangoPlay usage grows, the deployment architecture can evolve without changing the application's modular architecture.
Possible future improvements include:
- Multiple application instances
- Managed PostgreSQL
- Redis high availability
- Dedicated Celery workers
- Load balancing
- Automated rollback
- Containerized deployments
- Infrastructure-as-code
- Automated health checks
- Zero-downtime deployment
- Separate staging deployment
- Deployment observability
The current architecture deliberately does not introduce these components until operational requirements justify their complexity.
22. Deployment and Infrastructure Relationship
Deployment updates the application running on the infrastructure documented in the infrastructure architecture.
flowchart TD
DEPLOY["<b>GitLab Deployment</b>"]
VM["<b>GCP Production VM</b>"]
APP["DjangoPlay"]
CELERY["Celery"]
REDIS["Redis"]
DB["PostgreSQL"]
AUTHX["AuthX"]
DEPLOY --> VM
VM --> APP
VM --> CELERY
VM --> REDIS
VM --> DB
VM --> AUTHX
APP --> DB
APP --> REDIS
APP --> AUTHX
REDIS --> CELERY
classDef deploy fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a
classDef vm fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b
classDef auth fill:#f3eafa,stroke:#7b4bb7,stroke-width:2px,color:#542d82
class DEPLOY deploy
class VM,APP,CELERY,REDIS,DB vm
class AUTHX authDeployment is therefore an application delivery mechanism, while the infrastructure architecture describes the runtime environment in which the application operates.
23. Operational Responsibilities
| Area | Current Responsibility |
|---|---|
| Source repository | GitLab |
| CI/CD | GitLab CI/CD |
| Production approval | Manual GitLab deployment |
| Deployment transport | SSH |
| Production compute | GCP VM |
| Reverse proxy | Nginx |
| Application server | Gunicorn |
| Web application | DjangoPlay |
| Background processing | Celery |
| Cache / broker | Redis |
| Application database | PostgreSQL |
| Identity | AuthX |
| Static / asset delivery | Cloudflare / R2 |
| Repository mirror | GitHub |
| Python package registry | PyPI |
24. Related Documentation
The deployment architecture should be read together with the following documents:
| Document | Purpose |
|---|---|
djangoplay-infrastructure.md |
Production infrastructure and runtime topology |
architecture-style.md |
Overall architectural style |
app-architecture.md |
Standard Django application architecture |
authentication-architecture.md |
AuthX and authentication architecture |
security-architecture.md |
Security boundaries and controls |
integration-architecture.md |
Internal and external integrations |
scaling.md |
Scaling strategy and future growth |
env-configs.md |
Complete environment/configuration reference |
The environment configuration reference remains the authoritative source for individual configuration keys and their setup/runtime requirements.
25. Summary
DjangoPlay uses GitLab CI/CD as its primary delivery platform.
For the Django web application, the current production flow is:
GitLab
│
▼
main
│
▼
Manual GitLab Deployment
│
▼
SSH
│
▼
GCP Production VM
│
├── Pull latest main
├── Install dependencies
├── Run migrations
├── Collect static files
└── Restart Django + CeleryPython libraries use a separate package-release flow:
GitLab
│
▼
Test
│
▼
Build
│
▼
Version Check
│
▼
PyPIGitHub is maintained as a mirror through a GitLab-triggered GitHub Actions workflow and is not part of the primary production deployment path.
The current deployment architecture favors simplicity, explicit production control, and low operational overhead, while leaving room for future horizontal scaling and more automated deployment mechanisms.