Reviewed-on: http://178.131.50.201:3000/admin/dyolink/pulls/62 Reviewed-by: rameen <rameen.naghdi@gmail.com>
Dyolink
Monorepo: NestJS backend (backend/), Next.js frontend (frontend/), Docker stack under infrastructure/..
Local development: see backend/README.md and frontend/README.md.
Cursor AI: project conventions for agents are in AGENTS.md, .cursor/rules/, and .cursor/skills/.
Production deploy (Docker Hub + HTTPS + Let's Encrypt)
Full step-by-step guide: infrastructure/DEPLOY.md
Minimal server setup: install Docker, create .env + secrets/, docker login, run one script.
| On server (once) | In repo / Docker |
|---|---|
| DNS A record → server IP | docker-compose.prod.yml, nginx, certbot |
docker login (private Hub) |
Build & push images from dev machine |
secrets/database.env, secrets/backend.env |
Examples: database.prod.env.example, backend.prod.env.example |
infrastructure/.env (DOMAIN, LETSENCRYPT_EMAIL) |
deploy.prod.env.example |
Dev machine — build frontend with the public domain baked in, push to Docker Hub:
./infrastructure/scripts/build-and-push-prod.sh wixur.ir latest
Server — from infrastructure/:
cp deploy.prod.env.example .env # edit DOMAIN, paths
mkdir -p ../secrets && cp database.prod.env.example ../secrets/database.env
cp backend.prod.env.example ../secrets/backend.env # set passwords + FRONTEND_URL
docker login
chmod +x scripts/*.sh
./scripts/deploy-prod.sh
SSL is issued automatically via Certbot (scripts/init-letsencrypt.sh). Nginx config is generated from DOMAIN in .env. When you move to another domain (e.g. dyolink.com), update .env + backend.env, re-run init-letsencrypt.sh, and rebuild the frontend image with the new URL.
Deploy on your own server (Docker + Gitea)
High level: build container images → push to a registry → server pulls images and runs Compose. Optionally Gitea Actions automates that on every merge to main / master.
1. One-time server preparation
-
Install Docker and Docker Compose on the server.
-
Run Gitea with the container registry enabled (same host/port you use for
docker login, e.g.178.131.50.201:3000). -
Copy the repo (or deploy only
infrastructure/+ secrets). You need at least:infrastructure/docker-compose.registry.ymlinfrastructure/nginx/configs referenced by that compose fileinfrastructure/database/init.sqlif used by your Postgres service
-
Secrets on the server (never commit real values):
- Copy
infrastructure/database.staging.env.example→database.staging.env(Postgres user/password/db). - Copy
infrastructure/backend.staging.env.example→backend.staging.env(e.g.DATABASE_URL, JWT, pointing at the compose Postgres service name). - Put both files in one directory on the server, e.g.
/opt/dyolink/secrets/.
- Copy
-
Registry login from the server (same credentials you use for
docker push):docker login <registry-host>:<port> -u <user>For HTTP registries, Docker may require
insecure-registrieson the daemon.
2. Manual deploy (build images elsewhere, run on server)
On your dev machine (after successful local builds):
$REG = "<registry-host>:<port>"
$OWN = "<registry-owner>"
$TAG = "manual"
docker build -t "${REG}/${OWN}/dyolink-backend:${TAG}" -t "${REG}/${OWN}/dyolink-backend:latest" ./backend
docker build `
--build-arg NEXT_PUBLIC_API_URL="http://<your-public-ip>:<nginx-port>/api" `
--build-arg NEXT_PUBLIC_APP_URL="http://<your-public-ip>:<nginx-port>" `
--build-arg NEXT_PUBLIC_APP_NAME="Dyolink" `
-t "${REG}/${OWN}/dyolink-frontend:${TAG}" `
-t "${REG}/${OWN}/dyolink-frontend:latest" `
./frontend
docker push "${REG}/${OWN}/dyolink-backend:${TAG}"
docker push "${REG}/${OWN}/dyolink-backend:latest"
docker push "${REG}/${OWN}/dyolink-frontend:${TAG}"
docker push "${REG}/${OWN}/dyolink-frontend:latest"
On the server, from infrastructure/:
-
Create
deploy.registry.env(seeinfrastructure/deploy.registry.env.example):REGISTRY_PREFIX=<host>:<port>/<owner>(nohttp://, no trailing slash)IMAGE_TAG=latestor the tag you pushedSTAGING_HTTP_PORT=<host port>(e.g.8088— browser useshttp://<ip>:8088)
-
Set
DEPLOY_SECRETS_DIRto the absolute path of the folder containingdatabase.staging.envandbackend.staging.env(you can export it in the shell or add it todeploy.registry.envif your Compose setup expects it). -
Pull and start:
docker compose -f docker-compose.registry.yml --env-file deploy.registry.env pull backend frontend docker compose -f docker-compose.registry.yml --env-file deploy.registry.env up -d
The backend container runs prisma migrate deploy on startup (via entrypoint) when NODE_ENV=production, so schema updates apply after you deploy a new image that includes new migrations.
3. Automatic deploy (Gitea Actions)
Workflow file: .gitea/workflows/registry-build-deploy.yml.
Requirements:
-
Gitea Actions enabled for the repository.
-
A self-hosted runner (with Docker) registered to Gitea — the workflow uses
runs-on: self-hosted. -
Windows runners: the workflow uses PowerShell (not Bash). Gitea’s runner was failing with
execvpe(/bin/bash) failedwhen Bash was routed through WSL without a real/bin/bash. If your runner is Linux, switch.gitea/workflows/registry-build-deploy.ymltodefaults.run.shell: bashand use Bash syntax instead. -
Repository → Actions → Variables (examples):
REGISTRY_HOST— e.g.178.131.50.201:3000REGISTRY_OWNER— image namespace (same as Docker image path after the host), e.g.adminPUBLIC_BASE_URL— URL users open in the browser, e.g.http://178.131.50.201:8088(no trailing slash)DEPLOY_SECRETS_DIR— absolute path on the runner machine to the folder containingdatabase.staging.envandbackend.staging.env- Optional:
STAGING_HTTP_PORT(defaults to8088)
-
Repository → Actions → Secrets:
REGISTRY_USERNAMEREGISTRY_PASSWORD— access token with package read/write (or equivalent)
Trigger: push to main or master, or run the workflow manually (workflow_dispatch).
The pipeline clones from your Gitea instance, builds and pushes backend/frontend images, then on the runner runs docker compose pull and up -d using infrastructure/docker-compose.registry.yml.
Related paths
| Path | Role |
|---|---|
backend/Dockerfile |
API image |
frontend/Dockerfile |
Web image |
infrastructure/docker-compose.registry.yml |
Pull-only staging stack (registry images + nginx + postgres) |
infrastructure/deploy.registry.env.example |
Template for deploy.registry.env |