profileShare

rasmusjy / splitapp-backend-clean-onion

Read-only snapshot

No repository description.

main default branch 429 files Expires Sep 13, 2026, 9:06 AM
DEPLOY.md 4,478 bytes
1 # Deploy — travel.rasmusj.com
2
3 Production deploy on the Hetzner VPS. Caddy (already running in `/opt/caddy/`) terminates
4 HTTPS and reverse-proxies to this app's container over the shared `web` Docker network.
5
6 ```
7 Internet → Caddy (:80/:443, HTTPS) → [web network] → csweb-travel (:8080)
8 ↓ [internal network]
9 db (Postgres, no public port)
10 ```
11
12 ## Files in this repo
13
14 | File | Purpose |
15 |------|---------|
16 | `Dockerfile` | .NET 10 SDK build → aspnet runtime. Source stays under `SplitApp/`, so COPY paths are unchanged. |
17 | `docker-compose.prod.yml` | Production stack. No public ports; secrets from `.env`. |
18 | `.env.example` | Template. Copy to `.env` and fill with strong values. |
19 | `.env` | Real secrets — **gitignored, never commit.** Create on the server. |
20
21 ## Stage 1 — manual first deploy
22
23 ### 1. Deploy key (read-only access to the private repo)
24
25 ```bash
26 ssh <user>@<server-ip>
27 ssh-keygen -t ed25519 -C "csweb-travel-deploy" -f ~/.ssh/csweb-travel -N ""
28 cat ~/.ssh/csweb-travel.pub
29 ```
30
31 Add the printed public key to the GitHub repo → **Settings → Deploy keys → Add deploy key**
32 (read-only is enough). Then make git use it:
33
34 ```bash
35 cat >> ~/.ssh/config <<'EOF'
36
37 Host github-csweb-travel
38 HostName github.com
39 User git
40 IdentityFile ~/.ssh/csweb-travel
41 IdentitiesOnly yes
42 EOF
43 ```
44
45 ### 2. Clone
46
47 ```bash
48 cd /opt/projects
49 git clone git@github-csweb-travel:<your-user>/<repo>.git csweb-travel
50 cd csweb-travel
51 ```
52
53 ### 3. Secrets
54
55 Create `.env` in the repo root. Use the values prepared locally, or regenerate:
56
57 ```bash
58 cat > .env <<EOF
59 POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | cut -c1-32)
60 JWT_KEY=$(openssl rand -base64 48 | tr -d '\n')
61 SEED_ADMIN_PASSWORD=Adm!n.$(openssl rand -base64 9 | tr -d '/+=')9
62 EOF
63 chmod 600 .env
64 cat .env # note the SEED_ADMIN_PASSWORD — that's the admin@taltech.ee login
65 ```
66
67 ### 4. Build & start
68
69 ```bash
70 docker compose -f docker-compose.prod.yml up -d --build
71 docker compose -f docker-compose.prod.yml logs -f app # watch migrate + seed
72 ```
73
74 > ⚠️ The .NET build is the heaviest step. On a 4 GB server it should fit, but if the build
75 > gets OOM-killed, add temporary swap:
76 > `sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile`
77
78 Confirm the container is on the `web` network and healthy:
79
80 ```bash
81 docker network inspect web --format '{{range .Containers}}{{.Name}} {{end}}'
82 # should list: caddy ... csweb-travel
83 ```
84
85 ### 5. Caddy
86
87 Append to `/opt/caddy/Caddyfile`:
88
89 ```
90 travel.rasmusj.com {
91 reverse_proxy csweb-travel:8080
92 }
93 ```
94
95 > Note: target is `csweb-travel` (the container_name), not `web`. A unique name avoids
96 > DNS collisions once other apps join the shared `web` network.
97
98 Reload (no downtime for other sites):
99
100 ```bash
101 cd /opt/caddy
102 docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
103 # or, if the Caddyfile is bind-mounted and that fails:
104 docker compose restart caddy
105 ```
106
107 ### 6. Test
108
109 ```bash
110 curl -I https://travel.rasmusj.com # expect 200/302/308, valid Let's Encrypt cert
111 ```
112
113 Open https://travel.rasmusj.com and log in: `admin@taltech.ee` / `<SEED_ADMIN_PASSWORD from .env>`.
114
115 ## Updating later
116
117 ```bash
118 cd /opt/projects/csweb-travel
119 git pull
120 docker compose -f docker-compose.prod.yml up -d --build
121 ```
122
123 Migrations run automatically on boot (`MigrateDatabase=true`). Seeding is idempotent
124 (guards on existing rows), so it won't duplicate data on restart.
125
126 ## Security notes
127
128 - **DB password & JWT key** live only in `.env` (gitignored) and are injected as env vars.
129 `JWT__Key` overrides the placeholder in `appsettings.json`.
130 - **Seed admin password** is read from `SEED_ADMIN_PASSWORD` on first boot
131 (`AppDataInit.SeedIdentity`). This replaces the well-known course default `Kala.12345`,
132 so the public admin login is strong from the very first deploy.
133 - The other seeded demo accounts (`user@`, `alice@`, …) still use `Kala.12345`. They are
134 non-admin demo users. Remove them from `InitialData.cs` if you don't want demo logins.
135 - Postgres has **no published port** — it's reachable only by the app over `internal`.
136 - To rotate any secret: edit `.env`, then `docker compose -f docker-compose.prod.yml up -d`.
137 (Changing `SEED_ADMIN_PASSWORD` after first boot has no effect — the user already exists;
138 change that password through the app instead.)
139 ```
140