Raport: Migracja Railway → Mikrus

2026-07-18 v1.0 jash90 (bartekziimny90@gmail.com) Mikrus kate102 (Ubuntu 24.04, 8 GB RAM, 68 GB dysk)
Przygotowane przez: Hermes Agent · Źródła: railway list, railway metrics, GitHub API, Mikrus wiki

1. TL;DR

Railway miesięcznie
$16.78
3.3× hobby plan ($5)
Realnie zużywane
3.4 GB
z 8 GB dostępnych na Mikrusie
Projektów
11
9 Postgres · 4 Redis · 1 Qdrant
Przydzielone w Railway
128 GB
RAM + dysk, 97% zmarnowane
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.

2. Co jest na Railway

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
1squash-tmsquash (Java) + PostgresOnline
2vasculitis-xai-apiFastAPI (Python ML)Online
3accounting prodapi (NestJS) + PostgresOnline
4ai-gatewaybackend + Postgres + RedisOnline
5accounting devapi (NestJS) + PostgresOnline
6sesja-masterNext.js 15 + BunSleeping
7description-generatorapi + web + PostgresSleeping
8health-monitorapi (Formendo) + Postgres + Redisapi.formendo.plOnline
9citygame-backendapi (NestJS) + Postgres + RedisOnline
10pkd-searchbackend + frontend + Postgres + Qdrantkodypkd.app + www.kodypkd.appOnline
11ai-projectsbackend + frontend + Postgres + RedisSleeping frontend

Wolumeny (faktyczne użycie vs przydzielone)

ProjektWolumenUżywanePrzydzielone% marnotrawstwa
squash-tmpostgres-volume403.8 MB5000 MB
92%
accounting-prodpostgres-volume224.9 MB5000 MB
95%
accounting-devpostgres-volume231.1 MB5000 MB
95%
ai-gatewayredis-volume496.1 MB5000 MB
90%
ai-gatewaypostgres-volume217.6 MB5000 MB
96%
ai-projectspostgres-volume117.8 MB500 MB
76%
ai-projectsredis-volume48.8 MB500 MB
90%
citygame-backendpostgres-volume218.8 MB5000 MB
96%
citygame-backendredis-volume150.1 MB5000 MB
97%
description-generatorpostgres-volume216.1 MB5000 MB
96%
health-monitorpostgres-volume244.9 MB5000 MB
95%
health-monitorredis-volume200.6 MB5000 MB
96%
pkd-searchqdrant-volume-hqoS161.5 MB5000 MB
97%
pkd-searchpostgres-volume120.3 MB500 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.

3. Co jest na Mikrusie kate102.mikrus.xyz

ZasóbWartośćWniosek
OSUbuntu 24.04.4 LTSświeży
Kernel7.0.12-1-pve (Proxmox VE)VM, nie CT — pełny kernel
RAM8 GB (7.5 GB free)wystarczy na 12 kontenerów
Dysk79 GB total / 68 GB freezmieści 9× Postgres po 5 GB + system + image cache
Publiczny IPv4BRAK (NAT 192.168.1.102)→ Cloudflare Tunnel
Publiczny IPv62a01:4f9:3070:2844::102/128dostęp do świata
Łącze1 Gbps shared (Hetzner Helsinki)brak twardego limitu transferu, fair-use
Sprzęt fizycznyHetzner EX52-NVMe / AX-41 (128 GB RAM, NVMe, i7-8700 / Ryzen 5)współdzielone z innymi VPS
DockerMISSINGdo zainstalowania
Compose / PodmanMISSINGDocker + Compose v2
Reverse proxyMISSINGCaddy (auto-TLS)
IPv4 forward2 porty ogólne + 1 SSH + do 7 dodatkowych TCP/UDPplan B, jeśli CF Tunnel zawiedzie
codex / claude✅ zainstalowanedo późniejszej automatyzacji
ghklonowanie repo
railway CLI ~/.railway/bin/railway 5.27.0do dalszego odpytywania
Strych (backup)200 MB/serwer, rsync przez SSHza mały na moje potrzeby → R2
Storagedodatkowy dysk 125-1000 GBjuż mam /storage

4. Realne zużycie RAM (z railway metrics --since 24h)

Railway domyślnie daje 8 GB RAM per serwis. Mierzyłem średnie z 24h.

ProjektSerwisRAM avgLimit Railway% limitu
squash-tmsquash (Java)1.23 GB8 GB15%
squash-tmPostgres211 MB8 GB2.6%
vasculitis-xai-apiapi (Python)300 MB8 GB3.7%
accounting-prodapi (NestJS)192 MB8 GB2.4%
accounting-prodPostgres48 MB8 GB0.6%
ai-gatewaybackend101 MB8 GB1.3%
health-monitorapi (Formendo)356 MB8 GB4.5%
health-monitorPostgres97 MB8 GB1.2%
health-monitorRedis11 MB8 GB0.1%
citygame-backendcitygame-api65 MB8 GB0.8%
pkd-searchbackend109 MB8 GB1.4%
pkd-searchfrontend87 MB32 GB0.3%
pkd-searchQdrant367 MB8 GB4.6%
pkd-searchPostgres35 MB8 GB0.4%
ai-projectsbackend81 MB8 GB1.0%
ai-projectsRedis9.5 MB8 GB0.1%
description-generatorapi33 MB8 GB0.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.

5. Czym naprawdę są te aplikacje

Logi Railway i publiczne repo ujawniły, że aplikacje są czymś więcej niż myślałem.

accounting-app — to NestJS, nie plain Node

Logi Railway: [Nest] 1 - LOG [TaskDeadlineNotificationsService] Found 0 tasks due soon. Wbudowane schedulery:

Wniosek: kontener musi być włączony 24/7. Schedulery działają wewnątrz procesu API.

health-monitor — to pełna apka medyczna "Formendo", nie monitoring

Integracje w env vars:

Logi 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.

pkd-search — hardcoded Qdrant

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.

vasculitis-xai — Python ML stack

Publiczne jash90/vasculitis-xai: FastAPI + XGBoost/LightGBM/CatBoost + SHAP + LIME + LangChain + ChromaDB. Image python:3.11-slim ≈ 2-3 GB.

ai-gateway — Raccoon AI Gateway, publiczny SaaS

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.

citygame — NestJS 10 + R2 + Socket.IO

Publiczne jash90/citygame: NestJS 10 + Prisma + Socket.IO + Cloudflare R2 (już używają). Ma swój docker/ i własny compose.

ai-projects — NX monorepo z własnym 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ł.

sesja-master — Next.js 15 + Bun

Publiczne jash90/sesja-master: Next.js 15 + Bun, ma własny Caddyfile z dynamic port routing. Śpi na Railway.


6. Kody źródłowe — co publiczne, co prywatne

Publiczne (clone bez auth)Prywatne (wymaga gh auth)
  • jash90/sesja-master (583 MB)
  • jash90/citygame (3.7 MB)
  • jash90/ai-gateway (616 KB)
  • jash90/ai-projects (5.9 MB)
  • jash90/pkd-search-frontend (1.3 MB)
  • jash90/pkd-search-backend (198 KB)
  • jash90/vasculitis-xai (6.8 MB)
  • jash90/accounting-app (2 środowiska: prod+dev)
  • jash90/description-generator
  • jash90/health-monitor
user potwierdza dostęp

Domeny:


7. Squash TM — szczegółowa analiza

Połączony bezpośrednio z Railway Postgres Squash TM przez railway variable list -s Postgrespsql do maglev.proxy.rlwy.net:37929.

Test cases
690
100% WORK_IN_PROGRESS (drafty)
Test steps
2199
3.19 step/TC średnio
Execution count
0
nikt nigdy nie wykonał TC
DB size
151 MB
tabela action_test_step = 34 MB

Rozkład ważności (importance)

WażnośćLiczba%
HIGH30444%
MEDIUM27940%
LOW10716%

Zawartość kroków (expected_result)

Bucketn
< 100 znaków819
100-500525
500-2k165
2k-10k0
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).

Co to mówi o użyciu

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.

Sample TC (2167: STRUKTURA NAWIGACJI - DOCK)

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…"

Porównanie z alternatywami

NarzędzieFree tierCzy zmieści 690 TCMigracja effort
Squash TM (teraz)∞ self-host✅, ale overkill
Notion1 GB✅ (po wyciągnięciu base64 obrazów)30 min
Airtable1 GB / 5 users2h
TestRailtrial 30 dni1-2h
Qase3 users1h
GitHub Issues✅ (TC jako issues)30 min
Postgres + Next.js✅ (pełna kontrola)1-2 dni

Rekomendacja: 3 opcje

Najprostsze: Zostawić Squash na Railway + eksport XLSX do Notion jako archiwum. Łączny koszt: 30 min + grosze.

Jak wyciągnąć TC ze Squash (na wypadek migracji)

# 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. Plan migracji

8 faz, 5-6 dni. Squash na końcu (po decyzji).

Faza 0 — Przygotowanie hosta (Dzień 0, ~30 min)

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

Faza 1 — squash-tm (rozgrzewka)

1 kontener + Postgres. Najprostszy przypadek. Albo zostaje na Railway (rekomendacja).

Faza 2 — vasculitis-xai, sesja-master

Zero custom env vars. Wszystko w railway.toml/nixpacks.toml w repo. gh clone + budowa.

Faza 3 — accounting-dev

Ten sam jash90/accounting-app (NestJS). Test migracji na dev przed prod.

Faza 4 — description-generator

2 serwisy (api + web) + Postgres, monorepo jash90/description-generator.

Faza 5 — ai-gateway

Kod publiczny. Raccoon BYOK SaaS. Prisma + Redis + BullMQ + Stripe. Wymaga ostrożności z szyfrowaniem AES-256-GCM.

Faza 6 — health-monitor (Formendo), citygame, ai-projects

Schemat powtarzalny. Health-monitor ma custom domenę api.formendo.pl — trzeba zmienić A/AAAA w home.pl na nowy endpoint.

Faza 7 — pkd-search

Najtrudniejszy: Qdrant + Postgres + 2 serwisy + custom domeny kodypkd.app + www.kodypkd.app.

  1. Qdrant snapshot z Railway (REST API)
  2. Postgres restore
  3. Cutover domeny w panelu OVH
  4. Smoke test: pełen flow wyszukiwania PKD

Faza 8 — accounting-prod (cutover produkcyjny)

Krytyczny. W piątek po południu (bufor weekendowy na rollback):

  1. pg_dump prod, restore
  2. DNS cutover api-production-ffd9.up.railway.app → nowy endpoint
  3. Rotacja WSZYSTKICH sekretów (JWT, ENCRYPTION, AI_API_KEY) — stare wyciekły w dumpie CLI
  4. Czyszczenie Railway po 7 dniach stabilności: railway delete -p accounting-prod

9. Optymalizacja zasobów

A. Współdzielony Postgres (1 zamiast 9)

Każ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).

B. Współdzielony Redis (1 zamiast 4)

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

C. Qdrant dla pkd-search (NIE pgvector)

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

D. Squash TM — zostaje na Railway

1.23 GB RAM = 18% Mikrusa. Koszt $0.5-1/mies Railway. Lepiej zostawić niż rezygnować z 18% bufora RAM.

E. Health-monitor (Formendo) — pełen stack

NIE zamieniamy na Uptime Kuma. Produkcyjna płatna apka medyczna.

F. Multi-stage Dockerfiles

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.

G. Backupy — 7 dni lokalnie + 1× tygodniowo R2

#!/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.

H. Profile on-demand dla serwisów sleeping

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

Finalny stack

#KontenerImageRAMDysk
1postgres-sharedpostgres:17-alpine200 MB1.5 GB
2postgres-prodpostgres:17-alpine200 MB500 MB
3redis-sharedredis:7-alpine50 MB200 MB
4qdrant-pkdqdrant/qdrant50 MB200 MB
5-117× Node serwisynode:20-alpine multi-stage2 GB1.5 GB
12caddycaddy:2-alpine30 MB50 MB
13cloudflaredcloudflare/cloudflared30 MB30 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.


10. Disaster Recovery (minimum)

Bez tych elementów Mikrus jest jednopołówkowy: awaria = utrata wszystkiego.

Co wdrażam w Fazie 0

  1. Offsite backup na Cloudflare R2 (10 GB darmowe, S3-compatible). Bez tego: utrata Mikrusa = utrata wszystkich danych Railway.
  2. Test restore co miesiąc — wpis w kalendarzu, skrypt restore-test.sh.
  3. Monitoring + alerty — Uptime Kuma + Telegram/Discord webhook. Sprawdza co 60s, alert po 2× failure (3 min opóźnienia).
  4. SSH przez Tailscale (nie wystawiamy portu 22 na świat). 100 urządzeń za darmo.
  5. Dokumentacja odtwarzaniaRUNBOOK.md w /opt/hermes-config/notes/railway-migration/.
  6. Sekrety poza serwerem — po migracji do bitwarden albo /opt/hermes-config/secrets/ z chmod 600 + age szyfrowanie.

RTO/RPO po Fazie 0

AwariaRTORPO
Utrata pojedynczej bazy15 min0-24h
Utrata całego serwera1-2h0-24h
Włamanie3-4h (reinstall + restore + rotate)0-24h
Utrata Mikrusa (firma padnie)= postawienie nowego VPS + restore0-7d

Czego NIE daję (koszt)


11. Ryzyka i mitygacje

RyzykoPMitygacja
Brak publicznego IPv4 na MikrusiepewneCloudflare Tunnel (HTTPS-only, zero open portów)
68 GB dysku nie starczy na 9× Postgres 5GBśrednie2 GB per baza + R2 backup (wystarczy)
8 GB RAM nie udźwignie wszystkich Node + DBśrednieuruchamiać wg potrzeby; squash zostaje na Railway
Kod repo niepubliczny (3 prywatne)pewnegh auth login (user potwierdza dostęp)
Sekrety wyciekły w dumpie Railway CLIwysokiepełna rotacja po migracji
Cutover domeny kodypkd.app — downtimeśrednieDNS TTL=60 przed cutover, CF Load Balancer (stary+nowy) na przejście
vasculitis-xai image 2-3 GBśredniemulti-stage slim, cache warstw współdzielony
Włamanie na Mikrusa (SSH exposed)średnieTailscale SSH, klucze tylko, fail2ban

Sekrety do rotacji PO migracji


12. Do zrobienia (checklist)

Przed startem Fazy 0

W trakcie Fazy 0 (poza agentem)

Po migracji (musisz zrobić)

Po 7-14 dniach stabilności