Files
biztaghavisite/nodecloud-server-context.md
Ali Taghavi 29b70c6865
Some checks failed
Deploy to VPS / deploy (push) Has been cancelled
feat: add admin API routes for tags, timeline events, uploads, and uses management
- Implemented GET and POST endpoints for managing tags in `src/app/api/admin/tags/route.ts`.
- Created PUT and DELETE endpoints for timeline events in `src/app/api/admin/timeline/[id]/route.ts`.
- Added GET and POST endpoints for timeline management in `src/app/api/admin/timeline/route.ts`.
- Developed file upload functionality with validation in `src/app/api/admin/upload/route.ts`.
- Introduced PUT and DELETE endpoints for managing uses items in `src/app/api/admin/uses/[id]/route.ts`.
- Added GET and POST endpoints for uses management in `src/app/api/admin/uses/route.ts`.

feat: enhance admin UI components for better user experience

- Created `AdminSidebar` component for navigation in `src/components/admin/AdminSidebar.tsx`.
- Developed `ImageUpload` component for handling image uploads in `src/components/admin/ImageUpload.tsx`.
- Implemented `RichTextEditor` component for rich text editing in `src/components/admin/RichTextEditor.tsx`.
- Added `TagsInput` component for managing tags in `src/components/admin/TagsInput.tsx`.
- Created `TiptapRenderer` component for rendering HTML content in `src/components/writing/TiptapRenderer.tsx`.

feat: establish database interaction layer with Prisma

- Added database connection and session management in `src/lib/db.ts` and `src/lib/auth.ts`.
- Implemented CRUD operations for posts, products, projects, settings, timeline events, and uses items in respective files under `src/lib/db/`.
- Introduced utility functions for formatting dates and slug generation in `src/lib/types.ts`.
2026-05-03 14:07:58 +03:30

16 KiB

NODE-Cloud Server — Master Context for AI Assistants

Last updated: 2026-04-27 Owner: Ali Taghavi (@biztaghavi), CEO of NODE-Group (Novin Ofogh Dadeh Etemad) Purpose: Paste this into any new Claude/AI chat when working on this server or deploying new apps.


1. Server Overview

Field Value
Hostname srv9588446167
IP 193.105.234.35
OS Ubuntu 24.04.4 LTS
Kernel 6.8.0-107-generic
CPU 4 cores (KVM/QEMU)
RAM 3.8 GB
Disk 145 GB (29 GB used, 116 GB free)
Swap None
Virtualization KVM
SSH Port 22, root access
Docker 28.2.2 with Compose 2.37.1
Nginx 1.24.0
Certbot 2.9.0 with auto-renewal timer active

2. Iran-Specific Constraints (CRITICAL — Read Before Any Deployment)

This server is hosted in Iran. Several international services are blocked or unreliable:

2.1 Docker Images

  • Docker Hub is blocked. All images must use the ArvanCloud mirror: docker.arvancloud.ir/library/
  • Examples: docker.arvancloud.ir/library/node:20-alpine, docker.arvancloud.ir/library/mariadb:11, docker.arvancloud.ir/library/redis:7-alpine
  • Docker is configured with mirror: https://docker-mirror.liara.ir/

2.2 npm/Node.js Packages

  • npmjs.org is intermittently blocked. Use Liara's npm mirror: https://package-mirror.liara.ir/repository/npm/
  • npm v10 has a known bug ("Exit handler never called") — it silently fails and doesn't install packages. Use pnpm instead.
  • pnpm installation: Use corepack enable && corepack prepare pnpm@9 --activate with ENV COREPACK_NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/
  • Liara mirror rate-limits aggressively — use --network-concurrency 1 to avoid 429 errors. If rate-limited, wait 10-30 minutes and retry.
  • Fallback strategy for large projects: Install node_modules on a Mac/dev machine with unrestricted internet, then scp the tarball to the server. When doing this:
    • Install with npm install --platform=linux --arch=x64 --libc=musl to get Linux Alpine binaries
    • Also manually install native musl binaries: @next/swc-linux-x64-musl, lightningcss-linux-x64-musl, @tailwindcss/oxide-linux-x64-musl
    • Match SWC version to the exact Next.js version (check package.json)
    • Set ENV NEXT_SWC_DOWNLOAD_DISABLED=1 in Dockerfile to prevent Next.js from trying to download SWC

2.3 Alpine Linux Packages

  • apk add is blockeddl-cdn.alpinelinux.org is unreachable. Never use apk add in Dockerfiles. The base node:20-alpine image already has what's needed.

2.4 Google Services

  • Google Fonts are blocked at build time. If a project uses next/font/google, it must be replaced with next/font/local or @fontsource/* local packages.
  • fonts.googleapis.com is unreachable during Docker builds.

2.5 Prisma ORM

  • binaries.prisma.sh is blocked. Prisma cannot download its engine binaries during build.
  • Solution: Pre-download Prisma engine binaries on a machine with internet access, commit them to the repo in prisma-binaries/, and set these env vars in the Dockerfile:
    ENV PRISMA_QUERY_ENGINE_LIBRARY=/app/prisma-binaries/libquery_engine-linux-musl-openssl-3.0.x.so.node
    ENV PRISMA_SCHEMA_ENGINE_BINARY=/app/prisma-binaries/schema-engine-linux-musl-openssl-3.0.x
    ENV PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1
    ENV PRISMA_SKIP_POSTINSTALL_GENERATE=1
    
  • Both libquery_engine and schema-engine binaries are needed for linux-musl-openssl-3.0.x (Alpine).
  • Run prisma generate in the Dockerfile after copying binaries — it uses local files, no network needed.
  • For migrations, apply SQL files directly: docker exec -i <mysql_container> mariadb -u user -ppassword dbname < migration.sql

2.6 MySQL

  • MySQL 8.0+ Docker images crash on this server's CPU (no x86-64-v2 support). Use MariaDB 11 instead — fully MySQL-compatible.
  • MariaDB healthcheck: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]

2.7 SSL / Let's Encrypt

  • Let's Encrypt cannot reach this server directly for domains behind ArvanCloud CDN. Certbot fails with timeouts.
  • For domains behind ArvanCloud CDN: Use ArvanCloud's built-in SSL (set mode to "Full" or "Flexible"). No certbot needed.
  • For domains pointing directly to the server IP: Certbot works fine (e.g., pouyatebparto.ir, nodegroup.ir, hooranhr.ir).
  • A self-signed certificate exists at /etc/ssl/certs/nodecloud-selfsigned.crt and /etc/ssl/private/nodecloud-selfsigned.key — used for domains behind ArvanCloud where ArvanCloud handles public SSL.
  • IMPORTANT: Any new site behind ArvanCloud CDN MUST listen on port 443 with the self-signed cert in Nginx. Otherwise Nginx falls back to the first server block that has SSL (which is git.nodecloud.ir/Gitea) and serves wrong content.

3. Hosted Applications

3.1 Gitea (Self-hosted Git)

Field Value
Domain git.nodecloud.ir
Container gitea + gitea-db
Port 127.0.0.1:3000
Stack Gitea 1.25.5 + PostgreSQL 14
Compose /srv/nodecloud/gitea/docker-compose.yml
SSL Self-signed cert (ArvanCloud CDN handles public SSL)

3.2 Pouya Teb Parto (Medical Services)

Field Value
Domain pouyatebparto.ir
Container pouya_teb_app + pouya_teb_mysql + pouya_teb_phpmyadmin
Port 127.0.0.1:3001
Stack Next.js 16 + MariaDB 11 + phpMyAdmin
Compose /srv/nodecloud/apps/pouya-teb-parto/docker-compose.prod.yml
Git http://git.nodecloud.ir/pouyadolat/Pouya-teb-parto.git
SSL Let's Encrypt (certbot)
phpMyAdmin 127.0.0.1:8080 (SSH tunnel only)

3.3 NODE-Group Website

Field Value
Domain nodegroup.ir
Container nodegroup_web
Port 127.0.0.1:3002
Stack Static Next.js (exported to HTML, served by Nginx in container)
Compose /srv/nodecloud/apps/nodegroup/docker-compose.prod.yml
Git http://git.nodecloud.ir/nodegroup/nodegroupirwebsite.git
SSL Let's Encrypt (certbot)

3.4 Hooran HR (HR Gamification Platform)

Field Value
Domain hooranhr.ir, www.hooranhr.ir, hooranhr.com, www.hooranhr.com
Container hooranhr_app + hooranhr_mysql
Port 127.0.0.1:3003
Stack Next.js 16 + MariaDB 11 + DISC assessment
Compose /srv/nodecloud/apps/hooranhr/docker-compose.prod.yml
Git http://git.nodecloud.ir/nodegroup/hooranhrir.git
SSL Let's Encrypt (certbot, covers all 4 domains)

3.5 Khanehban (Building Management SaaS)

Field Value
Domain khanehbaan.ir
Container khanehbaan-app-1 + khanehbaan-mysql-1 + khanehbaan-redis-1
Port 127.0.0.1:3008
Stack Next.js 16 + MariaDB 11 + Redis 7 + Prisma ORM
Compose /srv/nodecloud/apps/khanehbaan/docker-compose.prod.yml
Git http://git.nodecloud.ir/nodegroup/khanehbaan-ir.git
SSL ArvanCloud CDN (self-signed cert on server, ArvanCloud "Full" mode)
S3 Storage Liara S3 (storage.c2.liara.space, bucket: buildingmanagement) — remains on Liara
Special Prisma with pre-downloaded binaries, node_modules transferred from Mac

3.6 Portainer (Docker Management UI)

Field Value
Domain manage.nodecloud.ir
Container portainer
Port 127.0.0.1:9000
Stack Portainer CE LTS
Compose /srv/nodecloud/apps/portainer/docker-compose.yml
SSL Self-signed cert (ArvanCloud CDN handles public SSL)

3.7 Nodecloud.ir (Gateway/Placeholder)

Field Value
Domain nodecloud.ir, www.nodecloud.ir
Type Static text response ("nodecloud main gateway is up")
Port 80 only (no SSL, no proxy)

4. Port Allocation Map

Port Service Notes
3000 Gitea Reserved
3001 Pouya Teb Parto Next.js app
3002 NODE-Group website Static Nginx
3003 Hooran HR Next.js app
3004-3007 Available
3008 Khanehban Next.js + Prisma
3009+ Available
8080 phpMyAdmin (Pouya Teb) SSH tunnel only
9000 Portainer Docker management UI

Next available port: 3004


5. Architecture Pattern for New Deployments

Every app follows this pattern:

Internet → ArvanCloud CDN (SSL) → Server:80/443 → Nginx → 127.0.0.1:PORT → Docker Container

5.1 Directory Structure

/srv/nodecloud/
├── gitea/                    # Gitea (separate, not under apps/)
│   └── docker-compose.yml
└── apps/
    ├── pouya-teb-parto/      # Each app gets its own directory
    │   ├── docker-compose.prod.yml
    │   ├── Dockerfile
    │   ├── .env
    │   └── ... (source code)
    ├── nodegroup/
    ├── hooranhr/
    ├── khanehbaan/
    └── portainer/

5.2 Standard Dockerfile Template (Next.js + pnpm)

FROM docker.arvancloud.ir/library/node:20-alpine AS builder
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
ENV PATH="/app/node_modules/.bin:$PATH"
ENV COREPACK_NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/

RUN corepack enable && corepack prepare pnpm@9 --activate

COPY package.json ./
RUN pnpm config set registry https://package-mirror.liara.ir/repository/npm/ && \
    pnpm install --no-frozen-lockfile --network-concurrency 1

COPY . .
RUN next build

FROM docker.arvancloud.ir/library/node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]

Note: next.config must have output: "standalone" for this to work.

5.3 Standard docker-compose.prod.yml Template

services:
  app:
    build: .
    container_name: APPNAME_app
    restart: unless-stopped
    ports:
      - "127.0.0.1:PORT:3000"
    env_file: .env
    environment:
      NODE_ENV: production

  # Only if app needs a database:
  mysql:
    image: docker.arvancloud.ir/library/mariadb:11
    container_name: APPNAME_mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 20

  # Only if app needs Redis:
  redis:
    image: docker.arvancloud.ir/library/redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  mysql_data:
  redis_data:

5.4 Nginx Config Template

For domains with direct DNS (certbot possible):

server {
    listen 80;
    listen [::]:80;
    server_name DOMAIN.ir www.DOMAIN.ir;

    client_max_body_size 20M;

    location / {
        proxy_pass http://127.0.0.1:PORT;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 60;
        proxy_send_timeout 60;
    }
}

Then: certbot --nginx -d DOMAIN.ir -d www.DOMAIN.ir

For domains behind ArvanCloud CDN (MUST include SSL block):

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name DOMAIN.ir www.DOMAIN.ir;

    ssl_certificate /etc/ssl/certs/nodecloud-selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/nodecloud-selfsigned.key;

    client_max_body_size 20M;

    location / {
        proxy_pass http://127.0.0.1:PORT;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 60;
        proxy_send_timeout 60;
    }
}

In ArvanCloud: SSL mode = "Full", origin = 193.105.234.35, purge cache after setup.


6. Deployment Checklist for New Sites

  1. Clone repo to /srv/nodecloud/apps/APPNAME/
  2. Fix Dockerfile for Iran: replace node:20-alpine with docker.arvancloud.ir/library/node:20-alpine, remove any apk add, use pnpm with Liara mirror
  3. Fix Google Fonts if present: grep for next/font/google and replace
  4. Create .env from .env.example with real credentials
  5. Use MariaDB 11 instead of MySQL 8.x
  6. Set port in docker-compose.prod.yml to next available (currently 3004+)
  7. Build: docker compose -f docker-compose.prod.yml build --no-cache
  8. Launch: docker compose -f docker-compose.prod.yml up -d
  9. Verify: curl -s http://127.0.0.1:PORT | head -5
  10. Run migrations if needed (apply SQL directly to MariaDB container)
  11. Create Nginx config in /etc/nginx/sites-available/, symlink to sites-enabled/
  12. SSL: Either certbot (direct DNS) or self-signed + ArvanCloud CDN
  13. Test: curl -I https://DOMAIN

7. Common Operations

Redeploy an app after code changes

cd /srv/nodecloud/apps/APPNAME
git pull
# Re-fix Dockerfile if git pull overwrote it
docker compose -f docker-compose.prod.yml build --no-cache
docker compose -f docker-compose.prod.yml up -d

View all container status

docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

View resource usage

docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

View logs for an app

cd /srv/nodecloud/apps/APPNAME
docker compose -f docker-compose.prod.yml logs -f app

Restart an app

cd /srv/nodecloud/apps/APPNAME
docker compose -f docker-compose.prod.yml restart

Access phpMyAdmin (Pouya Teb) via SSH tunnel

From your Mac:

ssh -L 8080:127.0.0.1:8080 root@193.105.234.35

Then open http://localhost:8080

Full server health check

echo "=== CONTAINERS ===" && docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" && echo "" && echo "=== RESOURCES ===" && docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

8. Firewall (UFW)

Only three ports are open:

  • 22 (SSH)
  • 80 (HTTP)
  • 443 (HTTPS)

All Docker container ports are bound to 127.0.0.1 — not publicly accessible. Nginx is the only public-facing service.


9. Known Issues & Gotchas

  1. git pull overwrites Dockerfile — After pulling, always re-apply Iran-specific fixes (image mirrors, apk removal, pnpm config). Consider committing a Dockerfile.prod to the repo instead.
  2. No swap configured — With 3.8 GB RAM and ~1.8 GB used, there's ~2 GB headroom. Adding more apps may require adding swap or upgrading RAM.
  3. DNS resolver on server is slowdig commands sometimes time out. This doesn't affect app operation, only manual DNS lookups from the server.
  4. ArvanCloud CDN caching — After any Nginx config change, always purge ArvanCloud cache and test in incognito.
  5. Self-signed cert is sharedgit.nodecloud.ir, manage.nodecloud.ir, and khanehbaan.ir all use the same self-signed cert. This is fine because ArvanCloud handles public SSL.
  6. Certbot auto-renewal is active but will only renew certs for domains with direct DNS (not behind ArvanCloud CDN).
  7. No automated backups — Docker volumes for databases are not backed up. Consider setting up periodic mysqldump cron jobs.
  8. Portainer times out if admin account isn't created quickly after restart. Run docker restart portainer and immediately access the UI.