Skip to content

Commit 343bdd7

Browse files
committed
add new deployment workflow for express app
1 parent 1cf6920 commit 343bdd7

File tree

3 files changed

+387
-0
lines changed

3 files changed

+387
-0
lines changed
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# Express Backend Deployment
2+
3+
## Overview
4+
5+
This document describes the modernized deployment strategy for the `@vue-skuilder/express` backend API.
6+
7+
## New Deployment Strategy
8+
9+
**Workflow**: `.github/workflows/deploy-express-backend.yml`
10+
**Service**: `express-backend.service`
11+
**Server**: eduquilt.com
12+
**Port**: 3000
13+
**Routes**: `/express/*` (via Caddy)
14+
**Deploy Path**: `/home/skuilder/express-backend``/home/skuilder/dist/express-backend/{N}`
15+
16+
### Key Improvements from Old Strategy
17+
18+
1. **Versioned Deployments**: Uses numbered versions (1, 2, 3...) instead of git SHAs
19+
- Easier to track and rollback
20+
- Automatic cleanup keeps last 5 versions
21+
22+
2. **Built Artifacts**: Deploys `dist/` output instead of source `src/`
23+
- Faster startup (no JIT compilation)
24+
- Production-ready code only
25+
26+
3. **Workspace Dependencies**: Properly handles monorepo dependencies
27+
- Builds and deploys `@vue-skuilder/common` and `@vue-skuilder/db`
28+
- Uses `file:` references for local packages
29+
30+
4. **Modern Node Environment**: Uses nvm-managed Node.js
31+
- Consistent with other services
32+
- Proper PATH setup in systemd
33+
34+
5. **Health Checks**: Verifies deployment before marking success
35+
- Automatically checks `/express` endpoint
36+
- Shows service status on failure
37+
38+
6. **Zero-Downtime**: Symlink swap ensures no service interruption
39+
40+
## Directory Structure on Server
41+
42+
```
43+
/home/skuilder/
44+
├── express-backend/ # Symlink to current version
45+
│ ├── dist/ # Built Express app
46+
│ ├── assets/ # Static assets
47+
│ ├── .env.production # Production environment variables
48+
│ ├── workspace/ # Monorepo dependencies
49+
│ │ ├── common/ # @vue-skuilder/common
50+
│ │ └── db/ # @vue-skuilder/db
51+
│ ├── package.json # Modified with file: refs
52+
│ └── node_modules/ # Production dependencies
53+
└── dist/
54+
└── express-backend/
55+
├── 1/ # Previous version
56+
├── 2/ # Previous version
57+
└── 3/ # Current version (symlinked)
58+
```
59+
60+
## First-Time Setup
61+
62+
### 1. Create systemd service
63+
64+
```bash
65+
# On the server
66+
sudo cp /home/skuilder/express-backend/express-backend.service /etc/systemd/system/
67+
sudo systemctl daemon-reload
68+
sudo systemctl enable express-backend
69+
```
70+
71+
**Note**: Verify Node.js path in the service file matches your nvm installation:
72+
```bash
73+
which node
74+
# Should be: /home/skuilder/.nvm/versions/node/v18.20.4/bin/node
75+
```
76+
77+
### 2. Verify Caddy routing
78+
79+
Check `/etc/caddy/Caddyfile` has:
80+
```
81+
handle_path /express/* {
82+
reverse_proxy localhost:3000
83+
}
84+
handle_path /express {
85+
reverse_proxy localhost:3000
86+
}
87+
```
88+
89+
### 3. GitHub Secrets Required
90+
91+
- `DO_SSH_KEY`: SSH private key for deployment
92+
- `DO_USERNAME`: SSH username (likely 'skuilder')
93+
- `KNOWN_HOSTS`: SSH known_hosts entry for eduquilt.com
94+
- `EXPRESS_ENV`: Production environment variables file content
95+
96+
The `EXPRESS_ENV` secret should contain all required environment variables:
97+
```bash
98+
COUCHDB_SERVER=localhost:5984
99+
COUCHDB_PROTOCOL=http
100+
COUCHDB_ADMIN=admin
101+
COUCHDB_PASSWORD=***
102+
VERSION=production
103+
NODE_ENV=production
104+
MAILER_SERVICE_URL=http://localhost:3001/mailer
105+
APP_URL=https://eduquilt.com
106+
SUPPORT_EMAIL=support@eduquilt.com
107+
```
108+
109+
## Deployment Process
110+
111+
1. **Trigger workflow** via GitHub Actions UI
112+
- Go to Actions → deploy-express-backend
113+
- Click "Run workflow"
114+
- Enter reason for deployment
115+
116+
2. **Workflow builds**:
117+
- Installs dependencies
118+
- Builds workspace packages (common, db)
119+
- Creates `.env.production` from `EXPRESS_ENV` secret
120+
- Builds Express backend
121+
- Creates build info
122+
123+
3. **Deploys to server**:
124+
- Syncs built artifacts (dist/, assets/, package.json)
125+
- Syncs `.env.production` file
126+
- Syncs workspace dependencies
127+
- Installs production dependencies
128+
- Updates symlink
129+
- Restarts service
130+
131+
4. **Verifies**:
132+
- Health check on `/express`
133+
- Shows service status if failed
134+
135+
5. **Cleanup**:
136+
- Removes old versions (keeps last 5)
137+
138+
## Rollback Strategy
139+
140+
```bash
141+
# On the server
142+
cd /home/skuilder/dist/express-backend/
143+
ls -1v # List versions
144+
145+
# Rollback to version N
146+
sudo ln -sfn /home/skuilder/dist/express-backend/N /home/skuilder/express-backend
147+
sudo systemctl restart express-backend
148+
149+
# Verify
150+
curl https://eduquilt.com/express
151+
systemctl status express-backend
152+
```
153+
154+
## Monitoring
155+
156+
```bash
157+
# Service status
158+
systemctl status express-backend
159+
160+
# Logs
161+
journalctl -u express-backend -f
162+
163+
# Check what version is running
164+
curl https://eduquilt.com/express
165+
166+
# Check symlink
167+
ls -la /home/skuilder/express-backend
168+
```
169+
170+
## Comparison with Old Strategy
171+
172+
| Aspect | Old (deprecated) | New |
173+
|--------|-----------------|-----|
174+
| Workflow | `deprecated-deploy-express.yml` | `deploy-express-backend.yml` |
175+
| Versioning | Git SHA | Sequential numbers |
176+
| Deploy Path | `/home/skuilder/api` | `/home/skuilder/express-backend` |
177+
| Artifacts | Source (`src/app.js`) | Built (`dist/app.js`) |
178+
| Node Runtime | System node | nvm-managed node |
179+
| Dependencies | Complex workspace resolution | Clean file: references |
180+
| Health Check | Version string grep | HTTP endpoint check |
181+
| Cleanup | Manual | Automatic (keep 5) |
182+
183+
## Migration from Old to New
184+
185+
**Do not run both services simultaneously** - they both bind to port 3000.
186+
187+
1. Stop old service: `sudo systemctl stop eqExpress`
188+
2. Deploy new version via GitHub Actions
189+
3. Verify new service: `systemctl status express-backend`
190+
4. If successful, disable old: `sudo systemctl disable eqExpress`
191+
5. Keep old service file as backup until confirmed stable
192+
193+
## Troubleshooting
194+
195+
### Service won't start
196+
```bash
197+
# Check service status
198+
systemctl status express-backend
199+
200+
# Check logs
201+
journalctl -u express-backend -n 50
202+
203+
# Common issues:
204+
# - Node path incorrect in service file
205+
# - Missing or invalid .env.production file
206+
# - Missing required environment variables (check logs)
207+
# - Port 3000 already in use (old service?)
208+
# - Workspace dependencies not installed
209+
210+
# Verify .env.production exists and has correct vars
211+
cat /home/skuilder/express-backend/.env.production
212+
# Should contain: COUCHDB_SERVER, COUCHDB_PROTOCOL, COUCHDB_ADMIN,
213+
# COUCHDB_PASSWORD, VERSION, NODE_ENV
214+
```
215+
216+
### Health check fails
217+
```bash
218+
# Test endpoint directly
219+
curl -v https://eduquilt.com/express
220+
221+
# Check if service is listening
222+
ss -tlnp | grep 3000
223+
224+
# Check Caddy routing
225+
sudo systemctl status caddy
226+
```
227+
228+
### Workspace dependency errors
229+
```bash
230+
# Verify workspace packages are present
231+
ls /home/skuilder/express-backend/workspace/
232+
233+
# Check package.json has file: references
234+
cat /home/skuilder/express-backend/package.json | grep "file:"
235+
236+
# Reinstall if needed
237+
cd /home/skuilder/express-backend
238+
NODE_ENV=production yarn install --production --immutable
239+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[Unit]
2+
Description=Vue-Skuilder Express API Backend
3+
After=network.target
4+
5+
[Service]
6+
Type=simple
7+
User=skuilder
8+
Group=skuilder
9+
WorkingDirectory=/home/skuilder/express-backend
10+
Environment=NODE_ENV=production
11+
Environment=PATH=/home/skuilder/.nvm/versions/node/v18.20.4/bin:/usr/local/bin:/usr/bin:/bin
12+
ExecStart=/home/skuilder/.nvm/versions/node/v18.20.4/bin/node /home/skuilder/express-backend/dist/app.js /home/skuilder/express-backend/.env.production
13+
Restart=always
14+
RestartSec=10
15+
StandardOutput=journal
16+
StandardError=journal
17+
SyslogIdentifier=express-backend
18+
19+
# Security settings
20+
NoNewPrivileges=true
21+
PrivateTmp=true
22+
23+
[Install]
24+
WantedBy=multi-user.target
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: deploy-express-backend
2+
on:
3+
workflow_dispatch:
4+
inputs:
5+
reason:
6+
description: 'Reason for deploying Express backend'
7+
required: true
8+
default: 'Manual deployment'
9+
jobs:
10+
deploy-express-backend:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Check out repository code
14+
uses: actions/checkout@v4
15+
16+
- name: Enable Corepack
17+
run: corepack enable
18+
19+
- name: Setup Node.js
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: 18
23+
cache: "yarn"
24+
25+
- name: Configure SSH key
26+
uses: shimataro/ssh-key-action@v2
27+
with:
28+
key: ${{ secrets.DO_SSH_KEY }}
29+
name: id_rsa
30+
known_hosts: ${{ secrets.KNOWN_HOSTS }}
31+
32+
- name: Install dependencies
33+
run: yarn install --immutable
34+
35+
- name: Build workspace dependencies
36+
run: |
37+
echo "Building @vue-skuilder/common..."
38+
yarn workspace @vue-skuilder/common build
39+
echo "Building @vue-skuilder/db..."
40+
yarn workspace @vue-skuilder/db build
41+
42+
- name: Set production environment
43+
working-directory: ./packages/express
44+
run: printf "${{ secrets.EXPRESS_ENV }}" > .env.production
45+
46+
- name: Build Express backend
47+
run: yarn workspace @vue-skuilder/express build
48+
49+
- name: Create build info
50+
run: |
51+
BUILD_INFO="Build created on $(date) from commit ${{ github.sha }}\n"
52+
BUILD_INFO+="Triggered by ${{ github.actor }} via ${{ github.event_name }}\n"
53+
BUILD_INFO+="Reason: ${{ github.event.inputs.reason }}\n"
54+
echo -e "$BUILD_INFO" > ./packages/express/dist/buildinfo.md
55+
56+
- name: Deploy to server
57+
run: |
58+
vcount=$(ssh ${{ secrets.DO_USERNAME }}@eduquilt.com ls -1v /home/skuilder/dist/express-backend/ | grep -E '^[0-9]+$' | tail -n1 || echo 0)
59+
newversion=$(($vcount+1))
60+
echo "Creating directory version $newversion..."
61+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com mkdir -p /home/skuilder/dist/express-backend/$newversion
62+
63+
echo "Syncing Express package..."
64+
rsync -rl --delete ./packages/express/dist/ ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/dist
65+
rsync -rl ./packages/express/package.json ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/
66+
rsync -rl ./packages/express/assets/ ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/assets
67+
rsync -rl ./packages/express/.env.production ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/.env.production
68+
69+
echo "Syncing workspace dependencies..."
70+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com mkdir -p /home/skuilder/dist/express-backend/$newversion/workspace
71+
72+
# Sync @vue-skuilder/common
73+
rsync -rl --delete ./packages/common/dist/ ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/workspace/common/dist
74+
rsync -rl ./packages/common/package.json ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/workspace/common/
75+
76+
# Sync @vue-skuilder/db
77+
rsync -rl --delete ./packages/db/dist/ ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/workspace/db/dist
78+
rsync -rl ./packages/db/package.json ${{ secrets.DO_USERNAME }}@eduquilt.com:/home/skuilder/dist/express-backend/$newversion/workspace/db/
79+
80+
echo "Installing production dependencies on server..."
81+
# Install deps for workspace packages first
82+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com "cd /home/skuilder/dist/express-backend/$newversion/workspace/common && source ~/.nvm/nvm.sh && NODE_ENV=production yarn install --production --immutable"
83+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com "cd /home/skuilder/dist/express-backend/$newversion/workspace/db && source ~/.nvm/nvm.sh && NODE_ENV=production yarn install --production --immutable"
84+
85+
# Create a modified package.json with file: references for workspace deps
86+
echo "Creating package.json with resolved workspace paths..."
87+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com "cd /home/skuilder/dist/express-backend/$newversion && cat package.json | jq '.dependencies[\"@vue-skuilder/common\"] = \"file:./workspace/common\" | .dependencies[\"@vue-skuilder/db\"] = \"file:./workspace/db\"' > package.json.tmp && mv package.json.tmp package.json"
88+
89+
# Install express package dependencies
90+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com "cd /home/skuilder/dist/express-backend/$newversion && source ~/.nvm/nvm.sh && NODE_ENV=production yarn install --production --immutable"
91+
92+
echo "Setting symlink to new version..."
93+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com ln -sfn /home/skuilder/dist/express-backend/$newversion /home/skuilder/express-backend
94+
echo "Deployment complete to version $newversion"
95+
96+
- name: Restart Express backend service
97+
run: |
98+
echo "Restarting Express backend service..."
99+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com "sudo systemctl restart express-backend || echo 'Service not found - will need manual setup'"
100+
101+
- name: Health check
102+
run: |
103+
echo "Waiting 10 seconds for service to start..."
104+
sleep 10
105+
echo "Testing Express backend..."
106+
if curl -f -s https://eduquilt.com/express > /dev/null; then
107+
echo "✅ Health check passed"
108+
echo "Response:"
109+
curl -s https://eduquilt.com/express
110+
else
111+
echo "❌ Health check failed - check service status"
112+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com "systemctl status express-backend || echo 'Service status check failed'"
113+
exit 1
114+
fi
115+
116+
- name: Clean old versions (keep last 5)
117+
run: |
118+
echo "Cleaning old deployment versions..."
119+
ssh ${{ secrets.DO_USERNAME }}@eduquilt.com '
120+
cd /home/skuilder/dist/express-backend/
121+
ls -1v | grep -E "^[0-9]+$" | head -n -5 | xargs -r rm -rf
122+
echo "Cleanup complete. Remaining versions:"
123+
ls -1v | grep -E "^[0-9]+$"
124+
'

0 commit comments

Comments
 (0)