- 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`.
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 --activatewithENV COREPACK_NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/ - Liara mirror rate-limits aggressively — use
--network-concurrency 1to avoid 429 errors. If rate-limited, wait 10-30 minutes and retry. - Fallback strategy for large projects: Install
node_moduleson a Mac/dev machine with unrestricted internet, thenscpthe tarball to the server. When doing this:- Install with
npm install --platform=linux --arch=x64 --libc=muslto 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=1in Dockerfile to prevent Next.js from trying to download SWC
- Install with
2.3 Alpine Linux Packages
apk addis blocked —dl-cdn.alpinelinux.orgis unreachable. Never useapk addin Dockerfiles. The basenode:20-alpineimage 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 withnext/font/localor@fontsource/*local packages. fonts.googleapis.comis unreachable during Docker builds.
2.5 Prisma ORM
binaries.prisma.shis 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_engineandschema-enginebinaries are needed forlinux-musl-openssl-3.0.x(Alpine). - Run
prisma generatein 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.crtand/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.configmust haveoutput: "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
- Clone repo to
/srv/nodecloud/apps/APPNAME/ - Fix Dockerfile for Iran: replace
node:20-alpinewithdocker.arvancloud.ir/library/node:20-alpine, remove anyapk add, use pnpm with Liara mirror - Fix Google Fonts if present: grep for
next/font/googleand replace - Create
.envfrom.env.examplewith real credentials - Use MariaDB 11 instead of MySQL 8.x
- Set port in
docker-compose.prod.ymlto next available (currently 3004+) - Build:
docker compose -f docker-compose.prod.yml build --no-cache - Launch:
docker compose -f docker-compose.prod.yml up -d - Verify:
curl -s http://127.0.0.1:PORT | head -5 - Run migrations if needed (apply SQL directly to MariaDB container)
- Create Nginx config in
/etc/nginx/sites-available/, symlink tosites-enabled/ - SSL: Either certbot (direct DNS) or self-signed + ArvanCloud CDN
- 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
- git pull overwrites Dockerfile — After pulling, always re-apply Iran-specific fixes (image mirrors, apk removal, pnpm config). Consider committing a
Dockerfile.prodto the repo instead. - 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.
- DNS resolver on server is slow —
digcommands sometimes time out. This doesn't affect app operation, only manual DNS lookups from the server. - ArvanCloud CDN caching — After any Nginx config change, always purge ArvanCloud cache and test in incognito.
- Self-signed cert is shared —
git.nodecloud.ir,manage.nodecloud.ir, andkhanehbaan.irall use the same self-signed cert. This is fine because ArvanCloud handles public SSL. - Certbot auto-renewal is active but will only renew certs for domains with direct DNS (not behind ArvanCloud CDN).
- No automated backups — Docker volumes for databases are not backed up. Consider setting up periodic
mysqldumpcron jobs. - Portainer times out if admin account isn't created quickly after restart. Run
docker restart portainerand immediately access the UI.