DEV Community

linou518
linou518

Posted on

Consolidating TechsFree Platform from 2 Servers to 1

Consolidating TechsFree Platform from 2 Servers to 1

Running a 9-node home cluster means you'll eventually lose track of which service is running where. Today I did a full audit and migrated everything from the infra server to the web server in one shot.


Background: Chaos Spread Across Two Servers

Our rough infra layout:

  • infra server (192.168.x.x): T440, message bus, dev environments, and "temporary" service storage
  • web server (192.168.x.x): CentOS with aaPanel, Nginx + SSL, production web server

The problem: the infra server became a permanent "temporary service storage." After auditing its ports, this is what I found:

Port Service Status
:3001 ERP API Backend (ex-online-shop) PM2, restarted 127 times
:5174 Shop Frontend (vite preview) PM2 managed
:5175 ERP Frontend (vite dev) Unmanaged process
:3400–3409 10 linebot experiment processes Unmanaged
:8080–8081 likeshop Docker Running on both servers
:8530 techsfree-homepage python http.server Left running "temporarily"

python3 -m http.server 8530 acting as a production server is... not ideal.


Step 1: Clean Up the Infra Server

First, killed all unnecessary processes:

# Kill 10 linebot experiment processes
ps aux | grep linebot | awk '{print $2}' | xargs kill

# Stop likeshop Docker containers on both servers
docker stop nginx node php mysql redis
docker stop php redis  # web server side

# Stop python http.server
pkill -f "http.server 8530"
Enter fullscreen mode Exit fullscreen mode

PM2 with restart: 127 is funny in retrospect, but it's proof of how long it was left unattended.


Step 2: Full Migration to Web Server

The TechsFree platform consists of:

  • Admin (ERP Dashboard): React SPA
  • Shop-PC: Nuxt3 SSR
  • Shop-H5: Vue3 SPA (mobile)
  • Backend: Node.js (Express + Prisma)
  • DB: MySQL (Docker managed)

All scattered between infra and web. Today I consolidated everything to the web server.

DB Migration

The first challenge was MySQL. The infra DB was buried inside a Docker bind mount from likeshop:

/path/to/likeshop/docker/data/mysql5.7.29/lib/techsfree_platform/
Enter fullscreen mode Exit fullscreen mode

Direct copy doesn't work due to Docker's internal format. Used a temporary container for a clean dump:

# Spin up a temporary MySQL container to dump
docker run --name tmp-mysql-dump \
  -v /path/to/mysql:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=<DB_PASSWORD> \
  -p 13307:3306 mysql:5.7 -d

mysqldump -h 127.0.0.1 -P 13307 -u root -p techsfree_platform > dump.sql

# Transfer and import to web server
scp dump.sql user@web-server:/tmp/
ssh user@web-server "mysql -h 127.0.0.1 -P 3308 -u root -p techsfree_platform < /tmp/dump.sql"
Enter fullscreen mode Exit fullscreen mode

37 tables, import successful.

Update Backend DB Connection

Copied the .env and updated only the DB host:

DB_HOST=127.0.0.1
DB_PORT=3308
DB_NAME=techsfree_platform
DB_USER=root
DB_PASSWORD=<DB_PASSWORD>
Enter fullscreen mode Exit fullscreen mode

Fired up with PM2:

✅ DB connection successful
✅ DB tables synced
🚀 TechsFree Platform Backend running: http://0.0.0.0:3210
Enter fullscreen mode Exit fullscreen mode

Frontend Build & Nginx Config

The Admin SPA had the old server IP hardcoded in VITE_API_URL. Fixed and rebuilt, then synced with rsync and updated Nginx:

# erp.techsfree.com.conf
location /api/ {
    proxy_pass http://127.0.0.1:3210/api/;
}
location / {
    root /www/wwwroot/erp.techsfree.com;
    try_files $uri $uri/ /index.html;
}
Enter fullscreen mode Exit fullscreen mode

Admin Password Reset

After migration, the admin password was unknown (had the bcrypt hash but not the original). Used bcryptjs to generate a new hash and updated directly in the DB:

node -e "
const bcrypt = require('./node_modules/bcryptjs');
console.log(bcrypt.hashSync('new-password', 10));
"
# → $2a$10$...
mysql -e "UPDATE tf_users SET password='\$2a\$10\$...' WHERE email='admin@techsfree.com'"
Enter fullscreen mode Exit fullscreen mode

Verified login via API — mission complete.


Step 3: Deploy Astro Blog

As a bonus, deployed the TechsFree tech blog (built with Astro). Hit two notable issues:

Pitfall 1: Can't npm install on NFS

The Astro project lives on /mnt/shared/ (NFS mount), which causes issues with symlinks and certain file ops. npm install failed midway.

Fix: copy to local /tmp/ first:

cp -r /mnt/shared/projects/04_techsfree-blog/ /tmp/blog-build/
cd /tmp/blog-build && npm install && npm run build
Enter fullscreen mode Exit fullscreen mode

Pitfall 2: Astro base Config and Subpath Serving

Setting base: '/blog' in Astro config caused internal links to break — subpages returned 404s everywhere.

Conclusion: skip the base config, serve on a dedicated port, and use Nginx proxy_pass:

location /blog/ {
    proxy_pass http://127.0.0.1:3300/;
}
Enter fullscreen mode Exit fullscreen mode

Pitfall 3: curl Test Giving Wrong Results

curl http://127.0.0.1/blog/ returned 404 and I panicked — but it was because Host: 127.0.0.1 was being picked up by the default server instead of the right vhost.

# Wrong: goes to default server with Host: 127.0.0.1
curl http://127.0.0.1/blog/

# Right: explicitly set the Host header
curl -H "Host: your-domain.com" http://127.0.0.1/blog/
Enter fullscreen mode Exit fullscreen mode

Spent 30 minutes wondering if it was a Nginx config bug or an Astro build issue. It wasn't either.


Lessons Learned

  1. Set expiry dates for temporary setups — never let python3 -m http.server become production
  2. Don't npm install on NFS — copy to /tmp/ first
  3. Mind the Host header in curl tests — in multi-vhost Nginx setups, always use -H "Host: xxx"
  4. Use temporary containers for DB migration — safer than directly manipulating bind mounts

If you don't periodically audit your infra, you'll end up with zombie processes that have restarted 127 times.


Tags: infra, server-ops, nginx, mysql, astro, nodejs, self-hosted, devops

Top comments (0)