LabelNex — Cloudflare Pages + GitHub Deployment Guide

shindesandip22 · labelnex.in · May 2026
Overview — What Gets Deployed Where
Subdomain GitHub Repo Host Source Status
${[ ["api.labelnex.in","shindesandip22/labelnex-api","Hetzner CX21","ZIP","pending"], ["kb.labelnex.in","shindesandip22/labelnex-kb","Hetzner CX21","ZIP","pending"], ["labelnex.in","shindesandip22/labelnex-main","Cloudflare Pages","GitHub","live"], ["tools.labelnex.in","shindesandip22/labelnex-tools","Cloudflare Pages","GitHub","live"], ["sandipshinde.com","shindesandip22/sandipshinde","Cloudflare Pages","GitHub","live"], ["docs.labelnex.in","shindesandip22/labelnex-docs","Cloudflare Pages","ZIP","pending"], ["admin2.labelnex.in","shindesandip22/labelnex-admin2","Cloudflare Pages","ZIP","pending"], ["portal.labelnex.in","shindesandip22/labelnex-portal","Cloudflare Pages","Source","pending"], ["studio.labelnex.in","shindesandip22/labelnex-studio","Cloudflare Pages","Source","pending"], ["admin.labelnex.in","shindesandip22/labelnex-admin","Cloudflare Pages","Source","pending"], ].map(([sub,repo,host,src,status])=>`
${sub} ${repo} ${host} ${src} ${status==='live'?'● LIVE':'○ PENDING'}
`).join('')}
Part 1 — Prerequisites (Do These Once)
1

Install Git + GitHub CLI on your Windows machine

Download Git from git-scm.com. Install with default options. Then install GitHub CLI from cli.github.com.

# Verify installation
git --version
gh --version

# Authenticate GitHub CLI
gh auth login
# Select: GitHub.com → HTTPS → Login with browser
2

Configure Git global identity

git config --global user.name "Sandip Shinde"
git config --global user.email "shindesandip2022@gmail.com"
git config --global core.autocrlf false
3

Cloudflare — Verify labelnex.in DNS zone

Log in to dash.cloudflare.com → labelnex.in → DNS. Ensure nameservers are active (check email from Cloudflare confirming activation). This is required before any CF Pages subdomain will resolve.

Part 2 — Deploy docs.labelnex.in (ZIP → GitHub → Cloudflare Pages)

This is the standard pattern for all ZIP-based deployments. Follow this exactly for docs, admin2, portal, studio, admin.

1

Extract ZIP to a local folder

# Open PowerShell in your build folder
cd "C:\Users\Admin\Documents\Startup SS Prep1\may-2026-current-build\deploy"

# Unzip docs
Expand-Archive -Path "docs.labelnex.in-v2.zip" -DestinationPath ".\docs-deploy" -Force
cd docs-deploy
2

Create GitHub repo and push

# Initialise git repo
git init
git add .
git commit -m "docs.labelnex.in v2 - initial deploy"

# Create GitHub repo and push (gh CLI does this in one command)
gh repo create shindesandip22/labelnex-docs --public --source=. --push

# Verify
gh repo view shindesandip22/labelnex-docs --web
3

Connect to Cloudflare Pages

Go to dash.cloudflare.comWorkers & PagesCreate applicationPagesConnect to Git

→ Select GitHub → Authorize Cloudflare → Select repo shindesandip22/labelnex-docs

→ Build settings: Framework preset: None | Build command: leave blank | Build output directory: / (or the folder with index.html)

→ Click Save and Deploy

4

Add custom domain docs.labelnex.in

In the Cloudflare Pages project → Custom domainsSet up a custom domain → Type docs.labelnex.inContinue

Cloudflare will automatically add the CNAME record to your DNS. Wait 2–5 minutes for propagation.

✓ Verify: open https://docs.labelnex.in in browser. Should load your docs site with SSL.
Part 3 — Deploy Sources (push existing source folder to GitHub)

For portal.labelnex.in, studio.labelnex.in, and admin.labelnex.in — the source is already in your local sources/ folder.

# Navigate to the portal source folder
cd "C:\Users\Admin\Documents\Startup SS Prep1\may-2026-current-build\sources\phase2-connected-surfaces-2026-04-29\labelnex-portal"

# Initialise, commit, create repo and push
git init
git add .
git commit -m "portal.labelnex.in - phase 2 connected surfaces"
gh repo create shindesandip22/labelnex-portal --public --source=. --push

# Repeat for studio
cd ..\labelnex-studio
git init && git add . && git commit -m "studio.labelnex.in initial"
gh repo create shindesandip22/labelnex-studio --public --source=. --push

# Repeat for admin
cd ..\labelnex-admin
git init && git add . && git commit -m "admin.labelnex.in initial"
gh repo create shindesandip22/labelnex-admin --public --source=. --push

After pushing each repo, connect it to Cloudflare Pages following the same steps in Part 2, Steps 3–4. Each gets its own custom domain.

Part 4 — Deploy API + KB to Hetzner via SSH
1

Set up SSH config on your Windows machine

# In PowerShell — generate SSH key if you don't have one
ssh-keygen -t ed25519 -C "sandip@labelnex-hetzner"
# Press Enter 3 times (accept defaults, no passphrase for automation)

# Add SSH config entry
notepad $HOME\.ssh\config

# Paste this into the config file:
Host labelnex-hetzner
  HostName YOUR_HETZNER_IP
  Port 22
  User root
  IdentityFile ~/.ssh/id_ed25519
⚠ Replace YOUR_HETZNER_IP with your actual Hetzner server IP from the Hetzner console. After running secure-hetzner.sh, change Port to 2222.
2

Copy your public key to Hetzner (first-time setup)

# In Hetzner console → your server → Rescue → Add your public key
# OR if you can already SSH as root:
cat $HOME\.ssh\id_ed25519.pub | ssh labelnex-hetzner "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
3

Upload ZIP files and run deploy scripts

# Copy deploy files to Hetzner (from your Windows build folder)
scp "labelnex-api-foundation-2026-05-06.zip" labelnex-hetzner:/tmp/
scp "labelnex-kb-portal-2026-05-06.zip" labelnex-hetzner:/tmp/
scp deploy-api-hetzner.sh labelnex-hetzner:/tmp/
scp deploy-kb.sh labelnex-hetzner:/tmp/
scp secure-hetzner.sh labelnex-hetzner:/tmp/

# SSH into Hetzner and run in order
ssh labelnex-hetzner

# On the server:
cd /tmp
chmod +x *.sh

# 1. Security first (do this BEFORE deploy)
bash secure-hetzner.sh
# IMMEDIATELY update SSH config on Windows to use port 2222 after this!

# 2. Deploy API
bash deploy-api-hetzner.sh

# 3. Edit .env with real passwords
nano /home/labelnex/.env
# Update: DB_PASSWORD, ANTHROPIC_API_KEY, BREVO_API_KEY

# 4. Start API with PM2
sudo -u labelnex pm2 start /home/labelnex/ecosystem.config.js
sudo -u labelnex pm2 save
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u labelnex --hp /home/labelnex

# 5. Deploy KB portal
bash deploy-kb.sh

# 6. Add KB to ecosystem.config.js (as instructed in deploy-kb.sh output)
nano /home/labelnex/ecosystem.config.js
# Add labelnex-kb app block to the apps array
sudo -u labelnex pm2 restart all
sudo -u labelnex pm2 save

# 7. Validate
curl https://api.labelnex.in/health
curl https://kb.labelnex.in/
Part 5 — DNS Records for All Subdomains

All Cloudflare Pages subdomains are added automatically when you add a custom domain in Cloudflare Pages. But if you need to add manually:

# All Cloudflare Pages subdomains get a CNAME record:
# TYPE  NAME                TARGET
CNAME   docs                YOUR-PROJECT.pages.dev
CNAME   admin2              YOUR-PROJECT.pages.dev
CNAME   portal              YOUR-PROJECT.pages.dev
CNAME   studio              YOUR-PROJECT.pages.dev
CNAME   admin               YOUR-PROJECT.pages.dev

# Hetzner-hosted subdomains get A records:
A       api                 YOUR_HETZNER_IP
A       kb                  YOUR_HETZNER_IP

# These should already exist (from existing deployments):
A/CNAME @                   (labelnex.in homepage - CF Pages)
CNAME   tools               (tools.labelnex.in - CF Pages)
A       sandipshinde.com    (CF Pages or separate)
⚠ In Cloudflare DNS, set the cloud icon (proxy status) to ORANGE (proxied) for all labelnex.in records. This enables Cloudflare's CDN, DDoS protection, and SSL. The api. and kb. subdomains pointing to Hetzner can be either proxied or DNS-only.
Part 6 — GitHub Actions CI/CD (Automatic Deploy on Push)

After initial setup, every git push to main should automatically redeploy. Here's the setup:

For Cloudflare Pages repos (docs, admin2, portal, studio, admin)

Cloudflare Pages automatically deploys on every push to the connected branch. Nothing extra needed. Check in Pages dashboard → Deployments to see build history.

For Hetzner repos (api, kb) — GitHub Actions workflow

# Create file: .github/workflows/deploy.yml in your labelnex-api repo

name: Deploy to Hetzner

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Hetzner via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.HETZNER_HOST }}
          username: labelnex
          key: ${{ secrets.HETZNER_SSH_KEY }}
          port: 2222
          script: |
            cd /home/labelnex/apps/labelnex-api
            git pull origin main
            npm install --production
            pm2 restart labelnex-api
            pm2 save

Add GitHub repository secrets: Settings → Secrets → Actions:

HETZNER_HOST = your Hetzner IP · HETZNER_SSH_KEY = contents of your ~/.ssh/id_ed25519 private key

⚠ For GitHub Actions SSH to work with port 2222 (after hardening), the GitHub Actions runner must be allowed through UFW. Run on Hetzner: ufw allow from 0.0.0.0/0 to any port 2222 (already done in secure-hetzner.sh — port 2222 is open).
Part 7 — Post-Deploy Validation Checklist

Run this checklist after deploying each subdomain:

api.labelnex.in/health → returns {"status":"ok","version":"..."}
kb.labelnex.in → loads knowledge base homepage
docs.labelnex.in → loads documentation site
admin2.labelnex.in → loads admin dashboard (login screen)
portal.labelnex.in → loads client portal
studio.labelnex.in → loads label studio
SSL check → all domains show 🔒 padlock (Cloudflare auto-SSL)
Uptime Kuma → all 10 monitors show green
Test IDoc → WE19 in SAP → LABELS01 → confirm api.labelnex.in/api/v1/idoc/receive returns 200
Cal.com booking → book a test call → confirm n8n workflow fires → HubSpot contact created → Brevo email received
Part 8 — Common Issues and Fixes
ProblemFix
${[ ["CF Pages shows 'Build failed'","Check Build output directory. For plain HTML sites, set to / or the subfolder containing index.html. Cloudflare expects an index.html at the root."], ["Custom domain shows 'Not found' after adding","Wait 5 minutes for DNS propagation. If still failing: CF Pages → Custom domains → verify the CNAME record was added to DNS. If not, add manually."], ["api.labelnex.in/health returns 502","Nginx is running but PM2 app is not. SSH to Hetzner: sudo -u labelnex pm2 status. If app is 'errored': sudo -u labelnex pm2 logs labelnex-api --lines 50"], ["SSL certificate error on Hetzner subdomain","Certbot failed during deploy. Run manually: certbot --nginx -d api.labelnex.in --email hello@labelnex.in --agree-tos. Ensure DNS A record points to Hetzner before running."], ["git push rejected: 'remote already exists'","gh repo create used wrong option. Fix: git remote remove origin && gh repo create shindesandip22/REPO --public --source=. --push"], ["Cloudflare Pages rebuild not triggered on push","Check GitHub → repo → Settings → Webhooks. Cloudflare webhook should be listed. If missing: disconnect and reconnect CF Pages to GitHub in CF Pages settings."], ["PM2 app not starting after server reboot","Run: sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u labelnex --hp /home/labelnex. Then: sudo -u labelnex pm2 save. Reboot and verify."], ].map(([prob,fix])=>`
${prob} ${fix}
`).join('')}
Quick Reference — Most Used Commands
# SSH to Hetzner
ssh labelnex-hetzner

# Check all PM2 processes
sudo -u labelnex pm2 status

# View API logs (last 50 lines)
sudo -u labelnex pm2 logs labelnex-api --lines 50

# Restart API after code update
sudo -u labelnex pm2 restart labelnex-api

# Check Nginx config
nginx -t

# Reload Nginx
systemctl reload nginx

# Check disk space
df -h

# Check latest backups
ls -lh /home/labelnex/backups/

# Update code from GitHub (if repo is cloned on server)
cd /home/labelnex/apps/labelnex-api && git pull origin main && npm install --production && pm2 restart labelnex-api

# Check UFW firewall status
ufw status verbose

# Check fail2ban banned IPs
fail2ban-client status sshd

# Manually run daily backup
bash /etc/cron.daily/labelnex-db-backup