Files
budget-app/docs/server-setup.md

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_key Woodpecker secret

Deploy directory:

  • The deploy user must have read/write access to the base deploy path (the value of the deploy_path Woodpecker secret, e.g. /var/www/budget-app)
  • The directory must exist before the first pipeline run
  • Subdirectories production/ and staging/ 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).

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