# Deployment: app.mydentalofficemanagement.com How this app is served in production: nginx serves a static production build on this machine, and Cloudflare Tunnel exposes it publicly at `https://app.mydentalofficemanagement.com` without opening any ports on the router. ``` Browser → Cloudflare (DNS + edge) → Tunnel (cloudflared, systemd) → nginx (:80) → static files ``` ## 1. Prerequisites - Debian (tested on Debian 13 "trixie") - sudo access - Node.js + pnpm installed, repo cloned - The domain `mydentalofficemanagement.com` already added as a zone in Cloudflare, with a Cloudflare account you can log into ## 2. Install cloudflared Debian's codename is usually too new for Cloudflare's apt repo, so install the `.deb` package directly instead: ```bash curl -fsSL -o /tmp/cloudflared.deb \ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb sudo dpkg -i /tmp/cloudflared.deb cloudflared --version ``` ## 3. Authenticate cloudflared ```bash cloudflared tunnel login ``` Opens a browser — log in and authorize the `mydentalofficemanagement.com` zone. This writes `~/.cloudflared/cert.pem`. ## 4. Tunnel This project uses an existing dashboard-managed tunnel named **`mydentalmanagement`** (tunnel ID `027669d4-3d96-4f90-920d-e727cecf3b92`). It already has other subdomains routed through it, so **reuse it** — don't create a new tunnel for this app. If it doesn't exist yet on a fresh Cloudflare account, create one: ```bash cloudflared tunnel create mydentalmanagement ``` ### Get a run token ```bash cloudflared tunnel token mydentalmanagement ``` Copy the printed token for the next step. (This token authenticates this machine to run the tunnel — treat it like a secret, don't commit it.) ### Install cloudflared as a service ```bash sudo cloudflared service install sudo systemctl enable --now cloudflared sudo systemctl status cloudflared ``` ## 5. Cloudflare public hostname route In the Cloudflare dashboard: **Zero Trust → Networks → Tunnels → mydentalmanagement → Published application routes → Add a published application route** | Field | Value | |---|---| | Hostname | `app.mydentalofficemanagement.com` | | Path | *(leave empty)* | | Service Type | `HTTP` | | URL | `localhost:80` | Note: `mydentalmanagement` was originally a locally-configured tunnel and had to be **migrated** to dashboard-managed mode the first time this was set up (Cloudflare shows a one-time "Migrate" prompt for this). That step is already done — future public hostname routes can just be added directly from this tab. If you see "A DNS record with this name already exists," a stale CNAME/A record is occupying that hostname — delete it in **DNS → Records** first, then re-add the route (this recreates the DNS record correctly as part of the route). ## 6. Install nginx ```bash sudo apt-get install -y nginx sudo rm -f /etc/nginx/sites-enabled/default ``` If Apache is installed and already bound to port 80, disable it first (only if nothing else depends on it): ```bash sudo systemctl disable --now apache2 ``` ### nginx site config Create `/etc/nginx/sites-available/app.mydentalofficemanagement.com.conf`: ```nginx server { listen 80; server_name app.mydentalofficemanagement.com; root /var/www/app.mydentalofficemanagement.com; index index.html; location / { try_files $uri $uri/ /index.html; } } ``` ```bash sudo ln -s /etc/nginx/sites-available/app.mydentalofficemanagement.com.conf \ /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx sudo systemctl enable nginx ``` Note: `/var/www/...` is used (not a path under `/home//...`) because nginx runs as `www-data`, and a typical home directory (`drwx------`) blocks that user from traversing into it. ## 7. Build and deploy the app `vite.config.ts` requires `PORT` and `BASE_PATH` env vars to even load (`PORT` isn't actually used by the static build, but the config throws without it regardless of command). ```bash cd "artifacts/dental-app" PORT=5173 BASE_PATH=/ npm run build ``` Deploy the build output: ```bash sudo mkdir -p /var/www/app.mydentalofficemanagement.com sudo cp -r dist/public/* /var/www/app.mydentalofficemanagement.com/ sudo chown -R www-data:www-data /var/www/app.mydentalofficemanagement.com ``` ### Redeploying after code changes Repeat the build + copy steps above any time the app changes: ```bash cd "artifacts/dental-app" PORT=5173 BASE_PATH=/ npm run build sudo cp -r dist/public/* /var/www/app.mydentalofficemanagement.com/ ``` ## 8. Running the site By default `nginx` and `cloudflared` are systemd services and can either auto-start on boot or be started manually. **Enable auto-start on boot:** ```bash sudo systemctl enable nginx sudo systemctl enable cloudflared ``` **Disable auto-start (manual start only):** ```bash sudo systemctl disable nginx sudo systemctl disable cloudflared ``` **Manually start the site** (needed after every reboot if auto-start is disabled): ```bash sudo systemctl start cloudflared sudo systemctl start nginx ``` **Stop the site:** ```bash sudo systemctl stop nginx sudo systemctl stop cloudflared ``` **Check status:** ```bash systemctl status nginx cloudflared curl -I https://app.mydentalofficemanagement.com ``` No Node/Vite process is needed to serve the site — it's a static build served directly by nginx. ## 9. Local development preview (not the public site) To preview changes locally before building/deploying, separate from the live public site: ```bash cd "artifacts/dental-app" PORT=5173 BASE_PATH=/ npm run dev ``` Then open `http://localhost:5173` in a browser on this machine. ## Troubleshooting - **502 from the public URL**: check `sudo journalctl -u cloudflared -n 40` for the exact error (often a port mismatch between the tunnel's Published Application Route and what nginx is actually listening on). - **nginx `502`/`403` locally**: check `sudo tail -n 30 /var/log/nginx/error.log`. - **`cloudflared tunnel list` shows "no active connection"**: the systemd service isn't running — `sudo systemctl status cloudflared`. - **Port 80 already in use**: check `sudo ss -tlnp | grep ':80 '` to see what's holding it (commonly Apache's default install).