VPS Auto-Deploy: CI/CD for Astro SSR and Rust
A step-by-step guide on configuring an automated git-driven CI/CD deployment pipeline on a VPS for Astro SSR and a Rust backend.
Setting up a CI/CD pipeline on a self-hosted VPS doesn't require complex or expensive external platforms. When deploying a storefront using Astro (SSR) for the frontend and Rust for the backend, you can configure a lightweight, automated deployment workflow using a simple webhook and a bash script.
This guide breaks down the configuration we used to set up a Git-driven, zero-overhead CI/CD pipeline on our Contabo VPS.
The Architectural Design
Our deployment workflow relies on GitHub Webhooks, an Apache reverse proxy, and a local bash handler managed via PM2.
Here is how the data flows from a developer's machine to the production environment:
+---------------------+
| Developer PC |
| |
| $ git push origin |
+----------+----------+
|
v
+---------------------+
| GitHub Repo |
| |
| Triggers Webhook |
+----------+----------+
| (POST request to port 443 HTTPS)
v
+-------------------------------------------------------------------------+
| VPS Server |
| |
| +--------------------+ |
| | Apache (httpd) | <--- Receives request at /deploy-webhook-example |
| | (Reverse Proxy) | |
| +---------+----------+ |
| | (Internal proxy to port 38416 localhost) |
| v |
| +--------------------+ |
| | aaPanel Webhook | <--- Validates access_key and triggers script |
| | Handler (Python) | |
| +---------+----------+ |
| | |
| v |
| +--------------------+ |
| | Bash Deploy | <--- 1. cd /opt/example-commerce |
| | Script | 2. git reset --hard && git pull |
| | | 3. npm install |
| | | 4. npm run build (storefront) |
| | | 5. pm2 restart example-frontend-ssr |
| +--------------------+ |
| |
+-------------------------------------------------------------------------+
1. Routing Webhooks with Apache (httpd)
The public reverse proxy on the VPS (Apache) handles SSL termination via Let's Encrypt / Cloudflare. We configured it to exclude the webhook URL path from the standard frontend proxy and forward it directly to the local webhook service listener (running on aaPanel's default script webhook port 38416).
Inside the virtual host configuration file:
# 1. Enable SSL Proxy Engine for internal loopback connection
SSLProxyEngine On
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off
# 2. Exclude webhook path from the default root proxy (/)
ProxyPass /deploy-webhook-example !
# 3. Route the public webhook path to aaPanel's local webhook port
RewriteEngine On
RewriteRule ^/deploy-webhook-example$ https://127.0.0.1:38416/hook?access_key=exampleWebhookAccessKey [P,L]
[!NOTE] Port
38416is the default local loopback port used by aaPanel's Webhook script plugin. If you are using a different panel or custom webhook receiver, replace this with the appropriate listener port configured on your VPS.
2. The aaPanel Webhook Automation Script
On the server, aaPanel coordinates incoming requests. When GitHub hits the /deploy-webhook-example endpoint, the webhook handler validates the access_key and launches the deployment script:
#!/bin/bash
echo "============================================="
echo "Starting automatic deploy process for Example Commerce..."
echo "Timestamp: $(date)"
# Export paths for Node, npm, and PM2
export PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/bin:$PATH
# Switch to the project directory
cd /opt/example-commerce || { echo "Failed to navigate to /opt/example-commerce"; exit 1; }
# Reset local changes and pull the latest code from Github
echo "Pulling the latest code from Github..."
git reset --hard
git pull origin main || { echo "Failed to execute git pull"; exit 1; }
# Install new dependencies (if package.json changed)
echo "Updating dependencies (npm install)..."
npm install || { echo "Failed to execute npm install"; exit 1; }
# Build the Astro storefront application
echo "Building the storefront (npm run build)..."
cd apps/storefront || { echo "Failed to navigate to apps/storefront"; exit 1; }
npm run build || { echo "Failed to build the storefront"; exit 1; }
# Restart the PM2 storefront process
echo "Resetting storefront PM2 process (example-frontend-ssr)..."
pm2 restart example-frontend-ssr || { echo "Failed to restart example-frontend-ssr"; exit 1; }
echo "Automatic deploy process finished successfully at $(date)"
echo "============================================="
3. Connecting the Astro SSR Frontend and Rust Backend
Our architecture splits the front and back of the store:
Frontend (Astro SSR): Dynamically pre-renders product pages and handles user interaction. Runs locally on port
3000via PM2.Backend (Rust/Actix-web): Exposes high-performance JSON API endpoints. Runs locally on port
8000as a compiled binary service.
To link them seamlessly without CORS issues, we configure Apache to forward any requests hitting shop.example.com/api/* straight to the Rust server:
ProxyPass /api http://127.0.0.1:8000/api
ProxyPassReverse /api http://127.0.0.1:8000/api
This ensures that the client-side code talks directly to the same origin, while the VPS splits the traffic behind the scenes.
4. Monitoring & Troubleshooting
To tail the deployment execution logs in real-time on the VPS and catch any Astro compilation errors:
tail -f /www/server/panel/plugin/webhook/script/exampleWebhookAccessKey.log
If you need to trigger a manual rebuild without pushing a new commit to GitHub, you can ping the webhook URL via Curl:
curl -i https://shop.example.com/deploy-webhook-example
Thanks for reading. See you in the next sharing.


