---
since: 1.2.1
---
# **DjangoPlay — Deployment & CI/CD Architecture**
---
## **1. Overview**
DjangoPlay uses **GitLab CI/CD as the primary source-control and deployment
automation platform**.
1. **DjangoPlay web application** — GitLab CI → manual production deployment over SSH → production GCP VM.
2. **Python/PyPI libraries** — GitLab CI → test → build → version check → publish to PyPI.
3. **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 `main` branch.
- GitLab connects to the production GCP VM over SSH.
- The production server pulls the latest `main` branch.
- 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:
```mermaid
flowchart LR
DEV["Developer"]
GITLAB["GitLab
Primary Repository"]
CI["GitLab CI/CD
Pipeline Orchestration"]
SSH["SSH Deployment
CI → Production VM"]
VM["GCP Production VM"]
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:
```text
Developer
│
▼
GitLab
│
▼
main
│
├──────────────► Production Deployment
│
└──────────────► GitHub Mirror
```
The 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.
```mermaid
flowchart LR
GITLAB["GitLab
Primary Repository"]
CI["GitLab CI
github-mirror.yml"]
DISPATCH["GitHub
repository_dispatch"]
ACTION["GitHub Actions
Mirror Workflow"]
GITHUB["GitHub Repository
Mirror"]
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 mirror
```
The 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**
```mermaid
flowchart TD
MAIN["GitLab main
Latest application code"]
PIPELINE["GitLab CI/CD"]
MANUAL["Manual Deploy
deploy_production"]
SSH["SSH
Production server"]
UPDATE["Update application
checkout + pull main"]
DEPS["Install dependencies
pip install -e .[dev]
djangoplay-cli"]
MIGRATE["Django migrations
manage.py migrate --noinput"]
STATIC["Collect static files
collectstatic --noinput"]
RESTART["Restart services
djangoplay + djangoplay-celery"]
LIVE["Production
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 production
```
---
# **5. 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:
```text
SERVER_HOST
```
The 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:
```text
/opt/djangoplay/.dplay/config.yaml
```
The deployment process reads the configured:
```text
webapp_dir
```
value 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:
```bash
git checkout main
git stash
git pull origin main
```
The production application therefore follows the GitLab `main` branch.
---
## **5.4 Install Dependencies**
The deployment environment activates the application's virtual environment:
```text
.venv
```
The deployment then installs:
```bash
pip install -e '.[dev]'
pip install djangoplay-cli
```
The 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:
```bash
python manage.py migrate --noinput
```
Database schema changes are therefore applied before the application services
are restarted.
---
## **5.6 Collect Static Files**
Production static files are collected using:
```bash
python manage.py collectstatic --noinput
```
The deployment does not require a separate manual static-file collection
step.
---
## **5.7 Restart Application Services**
The deployment completes by restarting:
```text
djangoplay
djangoplay-celery
```
The 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:
```mermaid
flowchart TD
CF["Cloudflare
DNS / Edge"]
NGINX["Nginx
Reverse Proxy / TLS"]
GUNICORN["Gunicorn
WSGI Server"]
DJANGO["DjangoPlay
Web Application"]
CELERY["Celery Worker
Background Processing"]
REDIS["Redis
Cache / Broker"]
POSTGRES["PostgreSQL
Application Database"]
AUTHX["AuthX
Identity Service"]
EXTERNAL["External Services
Email • AI • APIs • R2"]
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 external
```
---
# **7. 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:
```text
/opt/djangoplay/.dplay/
```
The primary configuration includes:
```text
config.yaml
.secrets
.authx
.appdata
```
The 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.
```text
Cloudflare
│
▼
GCP e2-micro VM
│
├── Nginx
├── Gunicorn / DjangoPlay
├── Celery
├── Redis
├── PostgreSQL
└── AuthX
```
The 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**
```text
GitLab main
│
▼
Manual production deployment
│
▼
SSH → GCP VM
│
▼
Django + Celery restart
```
## **11.2 Python Libraries**
Python libraries use a package-release pipeline:
```text
GitLab main
│
▼
Test
│
▼
Build
│
▼
Version Check
│
▼
Publish
│
▼
PyPI
```
This 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:
1. Test
2. Build
3. Version Check
4. Publish
The pipeline is reused across Python libraries.
```mermaid
flowchart LR
SOURCE["GitLab
main branch"]
TEST["Test
Ruff + Pytest"]
BUILD["Build
Python package artifacts"]
VERSION["Version Check
Semver + PyPI comparison"]
PUBLISH["Publish
Twine"]
PYPI["PyPI
Published package"]
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 publish
```
---
# **13. PyPI Test Stage**
The test stage uses a Python 3.11 CI image.
It performs:
```bash
pip install -e ".[dev]"
pip install pytest ruff
```
and then executes:
```bash
ruff check .
pytest --tb=short
```
The current shared library pipeline treats these checks as non-blocking by
using:
```bash
ruff check . || true
pytest --tb=short || true
```
The 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:
```bash
python -m build
```
The generated artifacts are stored under:
```text
dist/
```
and passed to subsequent jobs as CI artifacts.
Typical outputs include:
```text
*.whl
*.tar.gz
```
---
# **15. PyPI Version Check**
Before publishing, the pipeline:
1. Reads the version from `pyproject.toml`.
2. Validates the version format.
3. Queries PyPI for the currently published version.
4. 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:
```bash
twine upload dist/* --non-interactive
```
Authentication is supplied through GitLab CI/CD variables:
```text
PYPI_USERNAME
PYPI_PASSWORD
```
The conventional PyPI token username is:
```text
__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:
```text
Pipeline execution
│
├── Merge Request → validation
│
└── main → release eligible
```
---
# **18. GitLab CI Configuration Structure**
The CI configuration is intentionally split into reusable files.
For the DjangoPlay web application:
```text
.gitlab/
└── ci/
├── github-mirror.yml
└── deploy.yml
.gitlab-ci.yml
```
The root pipeline includes the individual CI definitions:
```yaml
include:
- local: '.gitlab/ci/github-mirror.yml'
- local: '.gitlab/ci/deploy.yml'
```
For Python libraries, the common structure is:
```text
.gitlab/
└── ci/
├── pypi-release.yml
└── github-mirror.yml
.gitlab-ci.yml
```
The 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:
```yaml
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
```
A 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:
```bash
--noinput
```
### **Service 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.
```mermaid
flowchart TD
DEPLOY["GitLab Deployment"]
VM["GCP Production VM"]
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 auth
```
Deployment 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:
```text
GitLab
│
▼
main
│
▼
Manual GitLab Deployment
│
▼
SSH
│
▼
GCP Production VM
│
├── Pull latest main
├── Install dependencies
├── Run migrations
├── Collect static files
└── Restart Django + Celery
```
Python libraries use a separate package-release flow:
```text
GitLab
│
▼
Test
│
▼
Build
│
▼
Version Check
│
▼
PyPI
```
GitHub 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.