5.9 KiB
Server Setup
This document describes the requirements for hosting the BudgetWise monorepo deployments. It is intentionally tool-agnostic — any reverse proxy that meets the requirements below will work.
1. SSH Deploy Access
CI pipelines connect to the server over SSH to transfer built files. A dedicated deploy user is required.
Requirements:
- A non-root system user (e.g.
deploy) with shell access - The CI deploy SSH public key added to that user's
~/.ssh/authorized_keys - SSH key must correspond to the
ssh_private_keyWoodpecker secret
Deploy directory:
- The deploy user must have read/write access to the base deploy path (the value of the
deploy_pathWoodpecker secret, e.g./var/www/budget-app) - The directory must exist before the first pipeline run
- Subdirectories
production/andstaging/must also exist and be writable - PR preview directories (
mr-{number}/) are created and deleted automatically by the CI — no pre-creation needed
/var/www/budget-app/ ← deploy_path (must exist, writable by deploy user)
production/ ← must exist
staging/ ← must exist
mr-42/ ← created by CI on PR open, deleted on PR close
mr-91/
...
2. DNS
Three types of hostnames are used:
| Hostname pattern | Environment | Example |
|---|---|---|
{domain} |
Production | budget.kushalgaywala.com |
staging.{domain} |
Staging | staging.budget.kushalgaywala.com |
{pr-number}.{domain} |
PR preview | 42.budget.kushalgaywala.com |
Requirements:
- An A (or AAAA) record pointing
{domain}to the server IP - A wildcard A record
*.{domain}pointing to the same server IP — this covers staging and all PR preview subdomains with a single record
3. TLS
Requirements:
- TLS certificates for
{domain}and*.{domain}(wildcard) - A wildcard certificate covers staging and all PR preview subdomains automatically, so no certificate management is needed per PR
- The wildcard certificate must be issued via a DNS-01 ACME challenge (HTTP-01 cannot validate wildcard domains)
4. Reverse Proxy
The reverse proxy sits in front of the deploy directories and routes incoming HTTPS requests to the correct static files.
4.1 Static file serving
Each environment maps a hostname to a directory of static HTML/JS/CSS files built by Vite.
| Hostname | Directory |
|---|---|
{domain} |
{deploy_path}/production/ |
staging.{domain} |
{deploy_path}/staging/ |
{pr-number}.{domain} |
{deploy_path}/mr-{pr-number}/ |
4.2 SPA fallback routing
The app is a single-page application (React Router). Any request for a path that does not match a physical file must fall back to serving index.html from that environment's directory.
Example: A request for /buckets/42 has no corresponding file on disk. The proxy must serve index.html instead of returning a 404, so React Router can handle the route client-side.
Requirement: For every environment, configure: "if the requested file does not exist, serve index.html from the same root directory."
4.3 Wildcard PR preview routing
The proxy must extract the PR number from the subdomain and map it to the correct directory dynamically — without needing a config change per PR.
Requirement: Match hostnames of the form {number}.{domain} and serve files from {deploy_path}/mr-{number}/, with the SPA fallback above applied.
Note: If a PR preview directory does not exist (e.g. the PR was closed and cleaned up), the proxy should return a 404 or a friendly error page rather than serving files from another environment.
4.4 HTTP → HTTPS redirect
Requirement: All plain HTTP requests must be redirected to HTTPS (301 or 308).
4.5 Headers (recommended)
| Header | Value | Reason |
|---|---|---|
X-Frame-Options |
DENY |
Prevent clickjacking |
X-Content-Type-Options |
nosniff |
Prevent MIME sniffing |
Referrer-Policy |
strict-origin-when-cross-origin |
Limit referrer leakage |
Cache-Control for index.html |
no-cache |
Ensure users get the latest app shell |
Cache-Control for assets/ |
public, max-age=31536000, immutable |
Vite hashes asset filenames, safe to cache forever |
5. Woodpecker CI
Woodpecker must be connected to the Gitea repo and have the following secrets configured under Repository → Settings → Secrets:
| Secret name | Description |
|---|---|
ssh_private_key |
Private SSH key for the deploy user |
deploy_host |
Server hostname or IP |
deploy_user |
SSH username on the server |
deploy_path |
Absolute base path, e.g. /var/www/budget-app |
prod_url |
Full production URL, e.g. https://budget.kushalgaywala.com |
prod_appwrite_endpoint |
Production Appwrite API endpoint |
prod_appwrite_project_id |
Production Appwrite project ID |
prod_appwrite_api_key |
Production Appwrite API key (databases.write + collections.write scopes) |
staging_url |
Full staging URL, e.g. https://staging.budget.kushalgaywala.com |
staging_appwrite_endpoint |
Staging Appwrite API endpoint |
staging_appwrite_project_id |
Staging Appwrite project ID |
staging_appwrite_api_key |
Staging Appwrite API key (same scopes) |
base_domain |
Base domain for PR previews, e.g. budget.kushalgaywala.com |
6. What CI Handles Automatically
Once the above is in place, no further manual server changes are needed for day-to-day use:
| Event | What happens automatically |
|---|---|
Push to main |
Web app built with production credentials and rsynced to production/ |
Push to staging |
Web app built with staging credentials and rsynced to staging/ |
| PR opened / updated | Web app built and deployed to mr-{number}/ |
| PR closed | mr-{number}/ directory removed from server |
apps/appwrite/** changed on main |
Appwrite schema deployed to production Appwrite project |
apps/appwrite/** changed on staging |
Appwrite schema deployed to staging Appwrite project |