Fix deploy config to match the actual VPS (Caddy, not Traefik)
The server fronts everything with Caddy (container root-caddy-1), has no Dockge and no Traefik, and keeps compose stacks in /docker — not /opt/stacks. Every traefik.* label in this compose file was inert, so the container would build and run while never being routed. Join the external `web` network Caddy is on so it can reach the container by name (info-share-spot:3000) and publish no host port. Routing now lives in /root/Caddyfile on the server. Rewrite DEPLOY.md against the real topology, including the Caddyfile validate-before-reload step — a bad Caddyfile takes down every site on the box, and Caddy serves a stale in-memory config until something reloads it, which hides the breakage until the next restart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,80 +1,92 @@
|
|||||||
# Deploying to a Hostinger VPS (Docker + Dockge + Traefik)
|
# Deploying to the avriahost VPS (Docker + Caddy)
|
||||||
|
|
||||||
This app is a TanStack Start (SSR) app. The Docker build produces a self-contained
|
This app is a TanStack Start (SSR) app. The Docker build produces a self-contained
|
||||||
Node server (`.output/server/index.mjs`) via the Nitro `node-server` preset and runs
|
Node server (`.output/server/index.mjs`) via the Nitro `node-server` preset and runs
|
||||||
it in a container. Traefik sits in front and gives it automatic HTTPS.
|
it in a container on port 3000. **Caddy** sits in front and gives it automatic HTTPS.
|
||||||
|
|
||||||
## 0. One-time: install the catalog apps
|
> Earlier revisions of this file described a Hostinger-catalog Docker + Dockge +
|
||||||
|
> Traefik setup. That is **not** what the server runs. There is no Dockge and no
|
||||||
|
> Traefik on this box, and `traefik.*` compose labels are silently ignored by
|
||||||
|
> Caddy — the container builds and runs perfectly while never being routed.
|
||||||
|
|
||||||
From hPanel → VPS → your server → **Catalog**, install:
|
## The actual server
|
||||||
|
|
||||||
1. **Docker** (base engine)
|
| | |
|
||||||
2. **Traefik** (reverse proxy + Let's Encrypt HTTPS)
|
|---|---|
|
||||||
3. **Dockge** (web UI to manage compose stacks + logs)
|
| Host | `2.25.155.250` (`srv1720881.hstgr.cloud`), hostname `avriahost`, Ubuntu 24.04 |
|
||||||
|
| Reverse proxy | Caddy, container `root-caddy-1`, owns :80/:443, automatic Let's Encrypt |
|
||||||
|
| Caddy config | `/root/Caddyfile` (compose project dir `/root`) |
|
||||||
|
| Compose stacks | `/docker/<name>` — **not** `/opt/stacks` |
|
||||||
|
| This stack | `/docker/info-share-spot` |
|
||||||
|
| Public URL | https://info-share-spot.srv1720881.hstgr.cloud |
|
||||||
|
| Git host | self-hosted Gitea at `http://2.25.155.250:32774` (SSH on port 44139) |
|
||||||
|
|
||||||
## 1. Point your domain at the VPS
|
Caddy shares the external `web` Docker network with this app, so it reaches the
|
||||||
|
container **by name** — `info-share-spot:3000`. The app therefore publishes no
|
||||||
|
host port at all.
|
||||||
|
|
||||||
In your DNS (Hostinger → Domains → DNS), add an **A record** for the subdomain you
|
## Routing
|
||||||
want (e.g. `app.yourdomain.com`) pointing at the VPS's public IP. Wait for it to
|
|
||||||
resolve before step 4 (Let's Encrypt needs it).
|
|
||||||
|
|
||||||
## 2. Find your Traefik values
|
The route lives in `/root/Caddyfile`:
|
||||||
|
|
||||||
The compose file has 3 placeholders to match to your Traefik install. On the VPS:
|
```caddyfile
|
||||||
|
info-share-spot.srv1720881.hstgr.cloud {
|
||||||
```bash
|
reverse_proxy info-share-spot:3000
|
||||||
# Traefik's docker network name (what to put for the `traefik` network):
|
}
|
||||||
docker network ls | grep -i traefik
|
|
||||||
|
|
||||||
# Traefik's entrypoint + certresolver names (look in its config/labels):
|
|
||||||
docker inspect traefik | grep -iE "entrypoints|certresolver|acme"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Typical values are network `traefik`, entrypoint `websecure`, resolver `letsencrypt`,
|
Always validate before reloading — a bad Caddyfile takes down *every* site on the
|
||||||
but **use whatever your install shows.** Edit `docker-compose.yml` accordingly and set
|
box, and Caddy keeps serving a stale in-memory config until something reloads it,
|
||||||
your real domain in the `Host(...)` rule.
|
which hides the breakage until the next restart:
|
||||||
|
|
||||||
## 3. Get the code onto the VPS as a Dockge stack
|
|
||||||
|
|
||||||
Dockge watches `/opt/stacks`. Clone the repo into a stack folder:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/stacks
|
docker exec root-caddy-1 caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||||
git clone https://github.com/renee-png/info-share-spot.git
|
docker exec -w /etc/caddy root-caddy-1 caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||||
```
|
```
|
||||||
|
|
||||||
The committed `.env` already holds the (public-safe) `SUPABASE_URL` and
|
## Deploying an update
|
||||||
`SUPABASE_PUBLISHABLE_KEY` that compose passes to the container at runtime.
|
|
||||||
|
|
||||||
## 4. Deploy in Dockge
|
|
||||||
|
|
||||||
1. Open Dockge → the `info-share-spot` stack appears automatically.
|
|
||||||
2. Confirm the 3 edited Traefik labels + domain look right.
|
|
||||||
3. Click **Deploy** (this runs `docker compose up -d --build`).
|
|
||||||
|
|
||||||
First build takes a few minutes (installs deps + builds). When it's up, visit
|
|
||||||
`https://app.yourdomain.com` — Traefik will have issued the TLS cert.
|
|
||||||
|
|
||||||
## 5. Create your admin account
|
|
||||||
|
|
||||||
The database (Supabase EDU project) is already migrated and empty, so the **first**
|
|
||||||
account you create at `/auth → Create account` becomes the **admin**.
|
|
||||||
|
|
||||||
## Updating later
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/stacks/info-share-spot
|
cd /docker/info-share-spot
|
||||||
git pull
|
git pull
|
||||||
|
docker compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Then in Dockge hit **Deploy** again (rebuilds the image and restarts). That's your
|
First build takes a few minutes (`bun install` + Vite/Nitro SSR build on 2 cores).
|
||||||
push-to-server loop: `git push` locally → `git pull` + Deploy on the VPS.
|
|
||||||
|
**`git pull` needs credentials**: the Gitea repo is private, so the VPS needs its
|
||||||
|
own read-only deploy key. The public key is at `/root/.ssh/gitea_deploy.pub`; add
|
||||||
|
it under the repo's Settings → Deploy Keys (read-only), then point the remote at
|
||||||
|
SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git remote set-url origin ssh://git@2.25.155.250:44139/admin/info-share-spot.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Until that key is installed, push code up from a workstation instead:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync -az --exclude .env.secret /path/to/info-share-spot/ root@2.25.155.250:/docker/info-share-spot/
|
||||||
|
ssh root@2.25.155.250 'chown -R root:root /docker/info-share-spot'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- The committed `.env` holds only the **public-safe** `SUPABASE_URL` and
|
||||||
|
`SUPABASE_PUBLISHABLE_KEY` (the publishable key is designed for browsers).
|
||||||
|
Compose substitutes them from that file at build and run time.
|
||||||
|
- Real secrets go in `.env.secret` next to the compose file — gitignored, loaded
|
||||||
|
by compose if present, never committed. Add `SUPABASE_SERVICE_ROLE_KEY` there
|
||||||
|
if you later use the server-side admin client.
|
||||||
|
- Supabase project is `qgmcpounpkgsjlwosjdi`. Note this is a **different** project
|
||||||
|
from the ACMACC one used by the avria.cloud app — don't cross the wires.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- **Google sign-in** still needs the Google provider enabled in the Supabase EDU
|
- **Google sign-in** needs the Google provider enabled in the Supabase dashboard
|
||||||
dashboard (Auth → Providers) and your production URL added under Auth → URL
|
(Auth → Providers) and the production URL added under Auth → URL Configuration.
|
||||||
Configuration. Email/password works without any of that.
|
Email/password works without any of that.
|
||||||
- **Container port** is 3000 (Traefik routes to it; you don't expose it publicly).
|
- The **first** account created at `/auth` becomes the admin. This has already
|
||||||
- **Service-role key**: not required. If you later use the server-side admin client,
|
happened on this project.
|
||||||
add `SUPABASE_SERVICE_ROLE_KEY` via Dockge's env editor — never commit it.
|
- `edu.avriahost.com` on this server returns 502 — it proxies to
|
||||||
|
`host.docker.internal:8080` and nothing listens there. Unrelated to this app.
|
||||||
|
|||||||
+15
-12
@@ -1,9 +1,13 @@
|
|||||||
# Deploy stack for the Hostinger VPS (Docker + Dockge + Traefik).
|
# Deploy stack for the avriahost VPS (Docker + Caddy).
|
||||||
# Lives in Dockge's stacks dir (/docker/info-share-spot) so Dockge manages it.
|
# Lives in /docker/info-share-spot.
|
||||||
#
|
#
|
||||||
# Traefik on this VPS runs in host-network mode with the Docker provider, so the
|
# This VPS fronts everything with Caddy (container `root-caddy-1`), NOT Traefik.
|
||||||
# app just needs traefik.enable + a router; no shared "traefik" network is needed.
|
# Caddy holds :80/:443, terminates TLS with automatic Let's Encrypt, and routes
|
||||||
# It's served at the VPS wildcard hostname with automatic Let's Encrypt HTTPS.
|
# by hostname from /root/Caddyfile. It shares the external `web` network with
|
||||||
|
# this app, so it reaches us as `info-share-spot:3000` — no published host port.
|
||||||
|
#
|
||||||
|
# Route lives in /root/Caddyfile:
|
||||||
|
# info-share-spot.srv1720881.hstgr.cloud { reverse_proxy info-share-spot:3000 }
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
@@ -22,10 +26,9 @@ services:
|
|||||||
SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_PUBLISHABLE_KEY}
|
SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_PUBLISHABLE_KEY}
|
||||||
HOST: 0.0.0.0
|
HOST: 0.0.0.0
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
labels:
|
networks:
|
||||||
- traefik.enable=true
|
- web
|
||||||
- traefik.http.routers.infoshare.rule=Host(`info-share-spot.srv1720881.hstgr.cloud`)
|
|
||||||
- traefik.http.routers.infoshare.entrypoints=websecure
|
networks:
|
||||||
- traefik.http.routers.infoshare.tls=true
|
web:
|
||||||
- traefik.http.routers.infoshare.tls.certresolver=letsencrypt
|
external: true
|
||||||
- traefik.http.services.infoshare.loadbalancer.server.port=3000
|
|
||||||
|
|||||||
Reference in New Issue
Block a user