Werdykt: da się. Mikrus (8 GB RAM) udźwignie wszystko po optymalizacji, zostanie ~5 GB zapasu. Squash-tm (1.23 GB RAM, 22 GB obrazów) — zdecydować czy zostawić na Railway, eksportować do Notion, czy migrować. Kluczowe: Cloudflare Tunnel (bo brak publicznego IPv4), 1 Gbps shared łącze Hetzner, brak limitu transferu.
11 projektów, każdy na jednym env production, region europe-west4-drams3a (Frankfurt). Wszystko replicas=1. Billing period: 2 lip – 2 sie 2026. Workspace jash90's Projects.
| # | Projekt | Serwisy | Custom domain | Status |
|---|---|---|---|---|
| 1 | squash-tm | squash (Java) + Postgres | — | Online |
| 2 | vasculitis-xai-api | FastAPI (Python ML) | — | Online |
| 3 | accounting prod | api (NestJS) + Postgres | — | Online |
| 4 | ai-gateway | backend + Postgres + Redis | — | Online |
| 5 | accounting dev | api (NestJS) + Postgres | — | Online |
| 6 | sesja-master | Next.js 15 + Bun | — | Sleeping |
| 7 | description-generator | api + web + Postgres | — | Sleeping |
| 8 | health-monitor | api (Formendo) + Postgres + Redis | api.formendo.pl | Online |
| 9 | citygame-backend | api (NestJS) + Postgres + Redis | — | Online |
| 10 | pkd-search | backend + frontend + Postgres + Qdrant | kodypkd.app + www.kodypkd.app | Online |
| 11 | ai-projects | backend + frontend + Postgres + Redis | — | Sleeping frontend |
| Projekt | Wolumen | Używane | Przydzielone | % marnotrawstwa |
|---|---|---|---|---|
| squash-tm | postgres-volume | 403.8 MB | 5000 MB | 92% |
| accounting-prod | postgres-volume | 224.9 MB | 5000 MB | 95% |
| accounting-dev | postgres-volume | 231.1 MB | 5000 MB | 95% |
| ai-gateway | redis-volume | 496.1 MB | 5000 MB | 90% |
| ai-gateway | postgres-volume | 217.6 MB | 5000 MB | 96% |
| ai-projects | postgres-volume | 117.8 MB | 500 MB | 76% |
| ai-projects | redis-volume | 48.8 MB | 500 MB | 90% |
| citygame-backend | postgres-volume | 218.8 MB | 5000 MB | 96% |
| citygame-backend | redis-volume | 150.1 MB | 5000 MB | 97% |
| description-generator | postgres-volume | 216.1 MB | 5000 MB | 96% |
| health-monitor | postgres-volume | 244.9 MB | 5000 MB | 95% |
| health-monitor | redis-volume | 200.6 MB | 5000 MB | 96% |
| pkd-search | qdrant-volume-hqoS | 161.5 MB | 5000 MB | 97% |
| pkd-search | postgres-volume | 120.3 MB | 500 MB | 76% |
| RAZEM | 3.05 GB | 55.18 GB | 94% zmarnowane | |
Kluczowa obserwacja: Railway zlicza przydzielone, nie używane. Te 9 małych Postgresów zajmuje 2.7 GB, ale przydzielone jest 45 GB. Po migracji i konsolidacji do 1 współdzielonego Postgresa z wieloma bazami, zejdziemy do ~2 GB.
kate102.mikrus.xyz| Zasób | Wartość | Wniosek |
|---|---|---|
| OS | Ubuntu 24.04.4 LTS | świeży |
| Kernel | 7.0.12-1-pve (Proxmox VE) | VM, nie CT — pełny kernel |
| RAM | 8 GB (7.5 GB free) | wystarczy na 12 kontenerów |
| Dysk | 79 GB total / 68 GB free | zmieści 9× Postgres po 5 GB + system + image cache |
| Publiczny IPv4 | BRAK (NAT 192.168.1.102) | → Cloudflare Tunnel |
| Publiczny IPv6 | 2a01:4f9:3070:2844::102/128 | dostęp do świata |
| Łącze | 1 Gbps shared (Hetzner Helsinki) | brak twardego limitu transferu, fair-use |
| Sprzęt fizyczny | Hetzner EX52-NVMe / AX-41 (128 GB RAM, NVMe, i7-8700 / Ryzen 5) | współdzielone z innymi VPS |
| Docker | MISSING | do zainstalowania |
| Compose / Podman | MISSING | Docker + Compose v2 |
| Reverse proxy | MISSING | Caddy (auto-TLS) |
| IPv4 forward | 2 porty ogólne + 1 SSH + do 7 dodatkowych TCP/UDP | plan B, jeśli CF Tunnel zawiedzie |
codex / claude | ✅ zainstalowane | do późniejszej automatyzacji |
gh | ✅ | klonowanie repo |
railway CLI | ✅ ~/.railway/bin/railway 5.27.0 | do dalszego odpytywania |
| Strych (backup) | 200 MB/serwer, rsync przez SSH | za mały na moje potrzeby → R2 |
| Storage | dodatkowy dysk 125-1000 GB | już mam /storage |
railway metrics --since 24h)Railway domyślnie daje 8 GB RAM per serwis. Mierzyłem średnie z 24h.
| Projekt | Serwis | RAM avg | Limit Railway | % limitu |
|---|---|---|---|---|
| squash-tm | squash (Java) | 1.23 GB | 8 GB | 15% |
| squash-tm | Postgres | 211 MB | 8 GB | 2.6% |
| vasculitis-xai-api | api (Python) | 300 MB | 8 GB | 3.7% |
| accounting-prod | api (NestJS) | 192 MB | 8 GB | 2.4% |
| accounting-prod | Postgres | 48 MB | 8 GB | 0.6% |
| ai-gateway | backend | 101 MB | 8 GB | 1.3% |
| health-monitor | api (Formendo) | 356 MB | 8 GB | 4.5% |
| health-monitor | Postgres | 97 MB | 8 GB | 1.2% |
| health-monitor | Redis | 11 MB | 8 GB | 0.1% |
| citygame-backend | citygame-api | 65 MB | 8 GB | 0.8% |
| pkd-search | backend | 109 MB | 8 GB | 1.4% |
| pkd-search | frontend | 87 MB | 32 GB | 0.3% |
| pkd-search | Qdrant | 367 MB | 8 GB | 4.6% |
| pkd-search | Postgres | 35 MB | 8 GB | 0.4% |
| ai-projects | backend | 81 MB | 8 GB | 1.0% |
| ai-projects | Redis | 9.5 MB | 8 GB | 0.1% |
| description-generator | api | 33 MB | 8 GB | 0.4% |
| RAZEM | ~3.4 GB | 128 GB | 2.7% | |
Realne zużycie 3.4 GB. Mikrus (8 GB) udźwignie wszystko z 4.6 GB zapasu. Squash sam zjada 36% — albo zostaje na Railway, albo kosztem bufora.
Logi Railway i publiczne repo ujawniły, że aplikacje są czymś więcej niż myślałem.
Logi Railway: [Nest] 1 - LOG [TaskDeadlineNotificationsService] Found 0 tasks due soon. Wbudowane schedulery:
TaskNotificationService co 60 min (overdue tasks)TaskDeadlineNotificationsService co 5 min (due-soon tasks)CustomFieldReminderService co 15 minWniosek: kontener musi być włączony 24/7. Schedulery działają wewnątrz procesu API.
Integracje w env vars:
P24_* — Przelewy24 (płatności polskie)RC_API_KEY — RevenueCat (subskrypcje iOS/Android)EXPO_ACCESS_TOKEN — Expo Push (notyfikacje)OPENAI_API_KEY — parsowanie głosu (/api/diet/parse-voice)SENTRY_DSN — błędyLogi Railway: ComplianceAlertService, DishCacheService, MealReminderService, MotivationalService. User-Agent: Formendo/1 CFNetwork/3860.500.112 Darwin/25.4.0 (apka iOS).
Wniosek: Produkcyjna płatna aplikacja dla użytkowników. NIE zamieniamy na Uptime Kuma.
Backend jash90/pkd-search-backend ma @qdrant/js-client-rest w zależnościach i używa go w src/services/. Nie da się pgvector bez przepisywania.
Frontend jash90/pkd-search-frontend ma już gotowy multi-stage Dockerfile (Node 22 + Caddy 2 alpine + SSG). Drop-in template.
Publiczne jash90/vasculitis-xai: FastAPI + XGBoost/LightGBM/CatBoost + SHAP + LIME + LangChain + ChromaDB. Image python:3.11-slim ≈ 2-3 GB.
Publiczne jash90/ai-gateway: NestJS 11 + Fastify + Prisma + Redis + BullMQ + Stripe. Multi-tenant BYOK proxy dla OpenAI/Anthropic/OpenRouter. AES-256-GCM envelope encryption dla kluczy klientów.
Publiczne jash90/citygame: NestJS 10 + Prisma + Socket.IO + Cloudflare R2 (już używają). Ma swój docker/ i własny compose.
Publiczne jash90/ai-projects: Nx + NestJS + Next.js + Bun. Ma już docker-compose.yml i docker-compose.monitoring.yml (Prometheus + Grafana + node-exporter + cadvisor) — to autor wiedział, że będzie migrował.
Publiczne jash90/sesja-master: Next.js 15 + Bun, ma własny Caddyfile z dynamic port routing. Śpi na Railway.
| Publiczne (clone bez auth) | Prywatne (wymaga gh auth) |
|---|---|
|
|
Domeny:
kodypkd.app — DNS na OVH (ns100.ovh.net), A=66.33.22.67formendo.pl — DNS na home.pl (dns.home.pl), A=46.242.240.126Połączony bezpośrednio z Railway Postgres Squash TM przez railway variable list -s Postgres → psql do maglev.proxy.rlwy.net:37929.
| Ważność | Liczba | % |
|---|---|---|
| HIGH | 304 | 44% |
| MEDIUM | 279 | 40% |
| LOW | 107 | 16% |
| Bucket | n |
|---|---|
| < 100 znaków | 819 |
| 100-500 | 525 |
| 500-2k | 165 |
| 2k-10k | 0 |
| 10k-100k (screenshoty) | 689 |
| > 100k (duży obraz) | 1 |
Wniosek: 690/690 expected_result ma data:image/jpeg;base64,… — każdy test case ma embedded screenshot oczekiwanego widoku ekranu z aplikacji. To są TC dla health-monitor (Formendo).
Squash TM jest WYŁĄCZNIE repozytorium test cases dla Formendo. Nie był używany jako TMS:To jest wiki/list do manualnej weryfikacji, nie aktywne narzędzie do zarządzania testami.
- 0 wykonań (executions) w 12+ miesięcy
- 0 kampanii, 0 iteracji
- 0 traceability do requirements
- 0 automation (nic nie zautomatyzowane)
- Wszystkie 690 TC =
WORK_IN_PROGRESS
Prerequisite: Pacjent zalogowany, dock widoczny (natywny), język PL.
Step 1: Odczytaj jej etykietę.
Expected: (pusty)
Step 2: Otwórz dock i zlokalizuj trzecią widoczną zakładkę (po Dziś i Postęp).
Expected: 📷 Oczekiwany widok ekranu (zrzut z aplikacji): [screenshot base64]
Step 3: Wejdź w tę zakładkę i sprawdź, czy prezentuje benefity, zniżki
i marketplace zgodnie ze specyfikacją.
Expected: WYMAGANIE WG SPEC (do uruchomienia po implementacji):
trzecia zakładka docka nazywa się "Korzyści…"
| Narzędzie | Free tier | Czy zmieści 690 TC | Migracja effort |
|---|---|---|---|
| Squash TM (teraz) | ∞ self-host | ✅, ale overkill | — |
| Notion | 1 GB | ✅ (po wyciągnięciu base64 obrazów) | 30 min |
| Airtable | 1 GB / 5 users | ✅ | 2h |
| TestRail | trial 30 dni | ✅ | 1-2h |
| Qase | 3 users | ✅ | 1h |
| GitHub Issues | ∞ | ✅ (TC jako issues) | 30 min |
| Postgres + Next.js | ∞ | ✅ (pełna kontrola) | 1-2 dni |
Najprostsze: Zostawić Squash na Railway + eksport XLSX do Notion jako archiwum. Łączny koszt: 30 min + grosze.
# Schema + data dump
railway connect Postgres -- pg_dump -Fc \
-t test_case -t test_step -t action_test_step \
-t test_case_steps -t test_case_library_node \
squash > squash-data.dump
# Albo CSV per tabela
psql "$DBURL" -c "\COPY test_case TO '/tmp/test_case.csv' CSV HEADER"
psql "$DBURL" -c "\COPY action_test_step TO '/tmp/steps.csv' CSV HEADER"
# Squash TM sam ma eksport UI: Settings → Export → Excel
8 faz, 5-6 dni. Squash na końcu (po decyzji).
apt update && apt upgrade -y
apt install -y docker.io docker-compose-plugin
systemctl enable --now docker
docker network create proxy
# Cloudflare Tunnel (poza agentem — wymaga konta CF)
cloudflared tunnel login
cloudflared tunnel create hermes-tunnel
cloudflared service install
# DNS: kodypkd.app, www.kodypkd.app, api.formendo.pl
# → CNAME <tunnel-id>.cfargotunnel.com
1 kontener + Postgres. Najprostszy przypadek. Albo zostaje na Railway (rekomendacja).
Zero custom env vars. Wszystko w railway.toml/nixpacks.toml w repo. gh clone + budowa.
Ten sam jash90/accounting-app (NestJS). Test migracji na dev przed prod.
2 serwisy (api + web) + Postgres, monorepo jash90/description-generator.
Kod publiczny. Raccoon BYOK SaaS. Prisma + Redis + BullMQ + Stripe. Wymaga ostrożności z szyfrowaniem AES-256-GCM.
Schemat powtarzalny. Health-monitor ma custom domenę api.formendo.pl — trzeba zmienić A/AAAA w home.pl na nowy endpoint.
Najtrudniejszy: Qdrant + Postgres + 2 serwisy + custom domeny kodypkd.app + www.kodypkd.app.
Krytyczny. W piątek po południu (bufor weekendowy na rollback):
pg_dump prod, restoreapi-production-ffd9.up.railway.app → nowy endpointrailway delete -p accounting-prodKażda baza < 250 MB, sumarycznie 2.3 GB → mieści się w jednym kontenerze. Każda ma własnego usera i hasło. Backup per-baza.
postgres-shared (1 kontener, image postgres:17-alpine)
├── baza: accounting_prod (user: acc_prod)
├── baza: accounting_dev (user: acc_dev)
├── baza: ai_gateway (user: ai_gw)
├── baza: citygame (user: citygame)
├── baza: description_gen (user: descgen)
├── baza: health_monitor (user: hmon)
├── baza: pkd_search (user: pkd)
├── baza: ai_projects (user: ai_proj)
├── baza: vasculitis (user: vasc)
└── baza: sesja (user: sesja)
Wyjątek: accounting_prod dostaje osobną instancję postgres-prod dla izolacji (krytyczny).
DB 0-3 dla 4 projektów. Konfiguracja per-projekt: REDIS_URL=redis://redis-shared:6379/N.
# redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru
Backend ma hardcoded @qdrant/js-client-rest. Snapshot via REST API:
curl -X POST "http://qdrant:6333/collections/pkd/snapshots"
curl "http://qdrant:6333/collections/pkd/snapshots/<id>" -o /opt/backups/qdrant/pkd-<date>.snapshot
curl -X PUT "http://qdrant:6333/collections/pkd/snapshots/upload?priority=snapshot" \
--data-binary @/opt/backups/qdrant/pkd-<date>.snapshot
1.23 GB RAM = 18% Mikrusa. Koszt $0.5-1/mies Railway. Lepiej zostawić niż rezygnować z 18% bufora RAM.
NIE zamieniamy na Uptime Kuma. Produkcyjna płatna apka medyczna.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]
Rezultat: 1.2 GB → 200 MB per obraz. Warstwy współdzielone między serwisami.
#!/bin/bash
DAY=$(date +%u)
DEST_LOCAL="/opt/backups/pg"
DEST_R2="r2:hermes-backups/weekly"
for db in accounting_prod accounting_dev ai_gateway citygame description_gen health_monitor pkd_search ai_projects vasculitis sesja; do
pg_dump -U postgres -Fc -Z 9 -d "$db" > "$DEST_LOCAL/${db}-$(date +%F).dump"
done
find "$DEST_LOCAL" -name "*.dump" -mtime +7 -delete
if [ "$DAY" = "7" ]; then
rclone copy "$DEST_LOCAL" "$DEST_R2/$(date +%G-W%V)/" --transfers 4
fi
pg_dump -Fc -Z 9 = custom + max compression. 230 MB text → 50 MB compressed. ~4.5× mniejsze.
4 serwisy śpią. Nie trzymać ich w pamięci. Compose profiles:
services:
sesja-master:
profiles: ["on-demand"]
# ...
Użycie: docker compose --profile on-demand up sesja-master
| # | Kontener | Image | RAM | Dysk |
|---|---|---|---|---|
| 1 | postgres-shared | postgres:17-alpine | 200 MB | 1.5 GB |
| 2 | postgres-prod | postgres:17-alpine | 200 MB | 500 MB |
| 3 | redis-shared | redis:7-alpine | 50 MB | 200 MB |
| 4 | qdrant-pkd | qdrant/qdrant | 50 MB | 200 MB |
| 5-11 | 7× Node serwisy | node:20-alpine multi-stage | 2 GB | 1.5 GB |
| 12 | caddy | caddy:2-alpine | 30 MB | 50 MB |
| 13 | cloudflared | cloudflare/cloudflared | 30 MB | 30 MB |
| RAZEM | ~2.7 GB | ~4.2 GB + image cache | ||
Zostaje ~5.3 GB RAM i ~63 GB dysk. Dużo zapasu na logi, backupy, przyszłe projekty.
Bez tych elementów Mikrus jest jednopołówkowy: awaria = utrata wszystkiego.
restore-test.sh.RUNBOOK.md w /opt/hermes-config/notes/railway-migration/.bitwarden albo /opt/hermes-config/secrets/ z chmod 600 + age szyfrowanie.| Awaria | RTO | RPO |
|---|---|---|
| Utrata pojedynczej bazy | 15 min | 0-24h |
| Utrata całego serwera | 1-2h | 0-24h |
| Włamanie | 3-4h (reinstall + restore + rotate) | 0-24h |
| Utrata Mikrusa (firma padnie) | = postawienie nowego VPS + restore | 0-7d |
| Ryzyko | P | Mitygacja |
|---|---|---|
| Brak publicznego IPv4 na Mikrusie | pewne | Cloudflare Tunnel (HTTPS-only, zero open portów) |
| 68 GB dysku nie starczy na 9× Postgres 5GB | średnie | 2 GB per baza + R2 backup (wystarczy) |
| 8 GB RAM nie udźwignie wszystkich Node + DB | średnie | uruchamiać wg potrzeby; squash zostaje na Railway |
| Kod repo niepubliczny (3 prywatne) | pewne | gh auth login (user potwierdza dostęp) |
| Sekrety wyciekły w dumpie Railway CLI | wysokie | pełna rotacja po migracji |
Cutover domeny kodypkd.app — downtime | średnie | DNS TTL=60 przed cutover, CF Load Balancer (stary+nowy) na przejście |
| vasculitis-xai image 2-3 GB | średnie | multi-stage slim, cache warstw współdzielony |
| Włamanie na Mikrusa (SSH exposed) | średnie | Tailscale SSH, klucze tylko, fail2ban |
JWT_SECRET, JWT_REFRESH_SECRET (accounting, citygame, health-monitor)ENCRYPTION_KEY, ENCRYPTION_SECRET (accounting, ai-gateway)AI_API_KEY_ENCRYPTION_KEY (accounting)MASTER_ENCRYPTION_KEY (ai-gateway — Raccoon BYOK!)SENTRY_DSN (wszystkie)SMTP_* (accounting, health-monitor)P24_*, RC_API_KEY, REVENUECAT_WEBHOOK_SECRET (health-monitor)OPENAI_API_KEY (health-monitor, pkd-search)EXPO_ACCESS_TOKEN (health-monitor)SEED_ADMIN_PASSWORD, SEED_EMPLOYEE_PASSWORD, SEED_OWNER_PASSWORDkodypkd.app i formendo.pl na CF NS, czy zostawić obecne NS-y i tylko dodać CNAME?cloudflared tunnel login + cloudflared tunnel create hermes-tunnelkodypkd.app, www.kodypkd.app, api.formendo.pl → CNAME tuneluhermes-backupsCORS_ORIGINS/CORS_WHITELIST w accounting, Formendo, citygamerestore-test.sh accounting_prodrailway delete -p accounting-prod i reszta projektów