434 lines
10 KiB
Markdown
434 lines
10 KiB
Markdown
# Qlockify Deployment
|
|
|
|
Main deployment and operations repository for Qlockify.
|
|
|
|
This repo is the entrypoint for running the full product stack in production.
|
|
|
|
## Related Repositories
|
|
|
|
- Deployment repository declared by `origin`: `https://git.amiirkhl.ir/Qlockify/qlockify-core-deployment.git`
|
|
- Backend repository declared by its `origin`: `https://git.amiirkhl.ir/Qlockify/qlockify-backend-deployment.git`
|
|
- Frontend repository declared by its `origin`: `https://git.amiirkhl.ir/Qlockify/qlockify-frontend-deployment.git`
|
|
|
|
Use this repo for:
|
|
|
|
- Docker Compose orchestration
|
|
- Nginx
|
|
- SSL certificate mounting
|
|
- domain routing
|
|
- environment layout
|
|
- production service startup
|
|
|
|
Use the backend and frontend repos for application-level implementation details.
|
|
|
|
## What This Repo Contains
|
|
|
|
- `docker-compose.yml`
|
|
- Nginx config
|
|
- Postgres support files
|
|
- Dockerfiles for production images
|
|
- deployment environment samples
|
|
- container networking and volume wiring
|
|
|
|
## Architecture
|
|
|
|
Main deployed services:
|
|
|
|
- `nginx`
|
|
- `frontend`
|
|
- `backend`
|
|
- `celery`
|
|
- `celery-beat`
|
|
- `redis`
|
|
- `db`
|
|
|
|
Traffic pattern:
|
|
|
|
- `qlockify.ir` serves the frontend
|
|
- backend traffic is served from `qlockify.ir` under `/api` and `/admin`
|
|
- Nginx terminates TLS and proxies requests to the frontend and backend containers
|
|
|
|
## Expected Repository Layout
|
|
|
|
Docker builds read from nested application directories inside this repository:
|
|
|
|
- `./backend/qlockify-backend-deployment`
|
|
- `./frontend/qlockify-frontend-deployment`
|
|
|
|
Expected layout:
|
|
|
|
```text
|
|
qlockify-deployment/
|
|
backend/
|
|
Dockerfile
|
|
.env.sample
|
|
qlockify-backend-deployment/
|
|
frontend/
|
|
Dockerfile
|
|
.env.sample
|
|
qlockify-frontend-deployment/
|
|
nginx/
|
|
postgres/
|
|
docker-compose.yml
|
|
```
|
|
|
|
## Deployment Flow
|
|
|
|
### 1. Place application source
|
|
|
|
Put the app repos into:
|
|
|
|
- `./backend/qlockify-backend-deployment`
|
|
- `./frontend/qlockify-frontend-deployment`
|
|
|
|
### 2. Configure env files
|
|
|
|
Create and fill:
|
|
|
|
- `./.env`
|
|
- `./backend/qlockify-backend-deployment/.env`
|
|
- `./frontend/qlockify-frontend-deployment/.env`
|
|
|
|
### 3. Build and run
|
|
|
|
```powershell
|
|
docker compose up -d --build
|
|
```
|
|
|
|
The backend container runs:
|
|
|
|
- database migrations
|
|
- `collectstatic`
|
|
- Gunicorn startup
|
|
|
|
## Domain and Routing
|
|
|
|
Configured domains:
|
|
|
|
- `qlockify.ir`
|
|
- `www.qlockify.ir`
|
|
|
|
Behavior:
|
|
|
|
- `www.qlockify.ir` redirects to `qlockify.ir`
|
|
- `http` redirects to `https`
|
|
- frontend is served from `qlockify.ir`
|
|
- backend traffic is proxied from `qlockify.ir/api` and `qlockify.ir/admin`
|
|
|
|
Before production startup:
|
|
|
|
1. Point DNS records for `qlockify.ir` and `www.qlockify.ir` to the server.
|
|
2. Make sure `80` and `443` are open on the server firewall.
|
|
3. Make sure the TLS certificate covers all required names.
|
|
|
|
## SSL Certificates
|
|
|
|
Place certificate files here:
|
|
|
|
```text
|
|
./nginx/certs/fullchain.pem
|
|
./nginx/certs/privkey.pem
|
|
```
|
|
|
|
The repository intentionally keeps only:
|
|
|
|
- `./nginx/certs/.gitkeep`
|
|
|
|
Real certificate files are ignored by git.
|
|
|
|
## Required Backend Environment
|
|
|
|
Set these in:
|
|
|
|
```text
|
|
./backend/qlockify-backend-deployment/.env
|
|
```
|
|
|
|
Core production values:
|
|
|
|
- `DJANGO_ALLOWED_HOSTS=qlockify.ir,www.qlockify.ir`
|
|
- `CORS_ALLOWED_ORIGINS=https://qlockify.ir,https://www.qlockify.ir`
|
|
- `CSRF_TRUSTED_ORIGINS=https://qlockify.ir,https://www.qlockify.ir`
|
|
- `BASE_URL=https://qlockify.ir`
|
|
- `POSTGRES_HOST=db`
|
|
- `REDIS_HOST=redis`
|
|
- `REDIS_URL=redis://redis:6379/0`
|
|
- `CELERY_BROKER_URL=redis://redis:6379/0`
|
|
- `CELERY_RESULT_BACKEND=redis://redis:6379/1`
|
|
|
|
Google OAuth values:
|
|
|
|
- `GOOGLE_OAUTH_CLIENT_ID=...`
|
|
- `GOOGLE_OAUTH_CLIENT_SECRET=...`
|
|
- `GOOGLE_OAUTH_REDIRECT_URI=https://qlockify.ir/api/users/oauth/google/callback/`
|
|
- `GOOGLE_OAUTH_FRONTEND_CALLBACK_URL=https://qlockify.ir/auth/google/callback`
|
|
|
|
## Required Frontend Environment
|
|
|
|
Set this in:
|
|
|
|
```text
|
|
./frontend/qlockify-frontend-deployment/.env
|
|
```
|
|
|
|
```text
|
|
VITE_API_BASE_URL=/api
|
|
```
|
|
|
|
## Background Workers
|
|
|
|
This stack includes:
|
|
|
|
- `celery` for async jobs
|
|
- `celery-beat` for scheduled jobs
|
|
|
|
If background scheduling stops working, inspect:
|
|
|
|
```powershell
|
|
docker compose logs -f celery
|
|
docker compose logs -f celery-beat
|
|
```
|
|
|
|
## Notifications and SSE
|
|
|
|
Notifications use Server-Sent Events at `/api/notifications/stream/`.
|
|
|
|
Current behavior:
|
|
|
|
- Nginx disables buffering for the SSE endpoint
|
|
- Gunicorn is tuned to tolerate connected streams for current traffic
|
|
- if concurrency grows materially, move SSE to async workers or a dedicated ASGI service
|
|
|
|
## Useful Operations
|
|
|
|
Build/rebuild:
|
|
|
|
```powershell
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Restart a subset:
|
|
|
|
```powershell
|
|
docker compose up -d --build nginx backend frontend
|
|
```
|
|
|
|
Inspect running services:
|
|
|
|
```powershell
|
|
docker compose ps
|
|
```
|
|
|
|
Follow logs:
|
|
|
|
```powershell
|
|
docker compose logs -f nginx
|
|
docker compose logs -f backend
|
|
docker compose logs -f celery
|
|
docker compose logs -f celery-beat
|
|
```
|
|
|
|
Stop everything:
|
|
|
|
```powershell
|
|
docker compose down
|
|
```
|
|
|
|
Backup the deployed data:
|
|
|
|
```bash
|
|
chmod +x ./scripts/backup.sh
|
|
./scripts/backup.sh
|
|
```
|
|
|
|
By default, backup archives are written to `./backups/`. Each archive contains:
|
|
|
|
- PostgreSQL data from the `db` service
|
|
- media files from the Docker media volume
|
|
- `.env` files from the deployment, backend, and frontend projects
|
|
|
|
Restore a backup archive on another machine:
|
|
|
|
```bash
|
|
chmod +x ./scripts/restore.sh
|
|
./scripts/restore.sh ./backups/qlockify-backup-YYYYMMDD-HHMMSS.tar.gz
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Restore is destructive for the database by default. To restore only part of an archive, use:
|
|
|
|
```bash
|
|
RESTORE_SKIP_DB=1 ./scripts/restore.sh ./backups/qlockify-backup-YYYYMMDD-HHMMSS.tar.gz
|
|
RESTORE_SKIP_MEDIA=1 ./scripts/restore.sh ./backups/qlockify-backup-YYYYMMDD-HHMMSS.tar.gz
|
|
RESTORE_SKIP_ENV=1 ./scripts/restore.sh ./backups/qlockify-backup-YYYYMMDD-HHMMSS.tar.gz
|
|
```
|
|
|
|
## CI/CD with Gitea Actions
|
|
|
|
This repository now ships with a Gitea Actions deployment workflow in:
|
|
|
|
- `.gitea/workflows/deploy.yml`
|
|
|
|
The backend and frontend repositories each ship with their own workflow files:
|
|
|
|
- backend: `.gitea/workflows/backend.yml`
|
|
- frontend: `.gitea/workflows/frontend.yml`
|
|
|
|
Deployment behavior:
|
|
|
|
- backend repo push to `main`: runs backend CI, then updates the backend checkout on the server and rebuilds `backend`, `celery`, and `celery-beat`
|
|
- frontend repo push to `main`: runs frontend lint/build, then updates the frontend checkout on the server and rebuilds `frontend`
|
|
- deployment repo push to `main`: validates deployment files, then updates the deployment checkout on the server and rebuilds `nginx` plus the app services
|
|
|
|
The remote deploy entrypoint is:
|
|
|
|
- `./scripts/deploy.sh`
|
|
|
|
### One-Time Server Bootstrap
|
|
|
|
Before Actions can deploy automatically, make sure the server is prepared once.
|
|
|
|
1. Clone all three repositories on the server into the expected layout:
|
|
|
|
```text
|
|
~/qlockify-deployment
|
|
~/qlockify-deployment/backend/qlockify-backend-deployment
|
|
~/qlockify-deployment/frontend/qlockify-frontend-deployment
|
|
```
|
|
|
|
2. Make sure the server can `git fetch` all three repositories non-interactively.
|
|
|
|
Recommended approach:
|
|
|
|
- add a deploy SSH key on the server
|
|
- add the public key to Gitea as a deploy key or a machine-user SSH key
|
|
- switch the server-side git remotes to SSH URLs
|
|
|
|
3. Pull the latest deployment repo once so the server has `scripts/deploy.sh`.
|
|
|
|
4. Make the deploy script executable:
|
|
|
|
```bash
|
|
chmod +x ~/qlockify-deployment/scripts/deploy.sh
|
|
```
|
|
|
|
5. Make sure the deploy user can run Docker Compose on the server.
|
|
|
|
### Gitea Runner Setup
|
|
|
|
Gitea Actions requires a trusted runner. Gitea's official docs describe the runner and label model here:
|
|
|
|
- Actions overview: `https://docs.gitea.com/usage/actions/overview`
|
|
- Act Runner: `https://docs.gitea.com/usage/actions/act-runner`
|
|
|
|
Recommended label setup for this project:
|
|
|
|
```text
|
|
qlockify-python:docker://python:3.14-bookworm
|
|
qlockify-node:docker://node:22-bookworm
|
|
qlockify-deploy:docker://ubuntu:24.04
|
|
```
|
|
|
|
Example non-interactive runner registration:
|
|
|
|
```bash
|
|
./act_runner register \
|
|
--no-interactive \
|
|
--instance https://git.amiirkhl.ir \
|
|
--token <runner_registration_token> \
|
|
--name qlockify-runner \
|
|
--labels "qlockify-python:docker://python:3.14-bookworm,qlockify-node:docker://node:22-bookworm,qlockify-deploy:docker://ubuntu:24.04"
|
|
```
|
|
|
|
Then start the runner daemon:
|
|
|
|
```bash
|
|
./act_runner daemon
|
|
```
|
|
|
|
### Gitea Secrets
|
|
|
|
Create these Actions secrets either at the `Qlockify` organization level or per repository.
|
|
|
|
- `SSH_PRIVATE_KEY`
|
|
- private key used by the workflow to SSH into the deployment server
|
|
- `SSH_KNOWN_HOSTS`
|
|
- output of:
|
|
|
|
```bash
|
|
ssh-keyscan -H <your-server-hostname-or-ip>
|
|
```
|
|
|
|
Do not create `GITEA_TOKEN` manually. Gitea provides a built-in job token and exposes it as `${{ secrets.GITEA_TOKEN }}`. See:
|
|
|
|
- `https://docs.gitea.com/usage/actions/token-permissions`
|
|
|
|
### Gitea Variables
|
|
|
|
Create these Actions variables in the Gitea UI:
|
|
|
|
- `DEPLOY_HOST`
|
|
- `DEPLOY_PORT`
|
|
- `DEPLOY_USER`
|
|
- `DEPLOY_PATH`
|
|
- `DEPLOY_BRANCH`
|
|
- `BACKEND_BRANCH`
|
|
- `FRONTEND_BRANCH`
|
|
|
|
Suggested values for your current server layout:
|
|
|
|
```text
|
|
DEPLOY_HOST=h9arjloaye
|
|
DEPLOY_PORT=22
|
|
DEPLOY_USER=ubuntu
|
|
DEPLOY_PATH=/home/ubuntu/qlockify-deployment
|
|
DEPLOY_BRANCH=main
|
|
BACKEND_BRANCH=main
|
|
FRONTEND_BRANCH=main
|
|
```
|
|
|
|
Gitea variables are available in workflows through `${{ vars.NAME }}`. Gitea documents that here:
|
|
|
|
- `https://docs.gitea.com/usage/actions/actions-variables`
|
|
|
|
### Recommended UI Setup
|
|
|
|
If all three repositories live under the same `Qlockify` organization, the cleanest setup is:
|
|
|
|
1. Add one organization-level runner to `Qlockify`
|
|
2. Add organization-level variables for the shared deploy target
|
|
3. Add organization-level secrets for the SSH key and known hosts
|
|
|
|
This keeps the three repositories consistent and avoids copying the same values three times.
|
|
|
|
### First Deploy Check
|
|
|
|
After creating the runner, variables, and secrets:
|
|
|
|
1. push the deployment repository first
|
|
2. confirm the deployment workflow succeeds
|
|
3. then push backend or frontend changes and confirm their repo-specific workflows deploy only the affected services
|
|
|
|
If a workflow fails on the server step, first check:
|
|
|
|
- runner logs
|
|
- repository Actions logs
|
|
- `docker compose logs -f backend`
|
|
- `docker compose logs -f frontend`
|
|
- `docker compose logs -f celery`
|
|
- `docker compose logs -f celery-beat`
|
|
|
|
## Scope Boundary
|
|
|
|
This repo should document:
|
|
|
|
- infrastructure
|
|
- runtime topology
|
|
- domains
|
|
- Nginx
|
|
- Docker Compose
|
|
- SSL
|
|
- operational startup and troubleshooting
|
|
|
|
It should not duplicate the application-specific implementation details already documented in the backend and frontend repositories.
|