Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Tiltfile β€” FinMind local Kubernetes dev workflow
# Usage: tilt up
# Prerequisites: Docker, kubectl, a local K8s cluster (kind / minikube / Docker Desktop)

load('ext://helm_resource', 'helm_resource', 'helm_repo')
load('ext://restart_process', 'docker_build_with_restart')

# ── Build images ──────────────────────────────────────────────────────────────
docker_build(
'finmind-backend',
context='./packages/backend',
dockerfile='./packages/backend/Dockerfile',
live_update=[
sync('./packages/backend', '/app'),
run('pip install -r /app/requirements.txt', trigger='./packages/backend/requirements.txt'),
],
)

docker_build(
'finmind-frontend',
context='./app',
dockerfile='./app/Dockerfile',
live_update=[
sync('./app/src', '/app/src'),
],
)

# ── Apply K8s manifests ───────────────────────────────────────────────────────
k8s_yaml([
'deploy/k8s/namespace.yaml',
'deploy/k8s/app-stack.yaml',
'deploy/k8s/monitoring-stack.yaml',
])

# ── Resource grouping ─────────────────────────────────────────────────────────
k8s_resource('finmind-backend', port_forwards='8000:8000', labels=['backend'])
k8s_resource('finmind-frontend', port_forwards='3000:80', labels=['frontend'])
k8s_resource('postgres', port_forwards='5432:5432', labels=['data'])
k8s_resource('redis', port_forwards='6379:6379', labels=['data'])

# ── One-time DB migration ─────────────────────────────────────────────────────
local_resource(
'db-migrate',
cmd='kubectl exec -n finmind deploy/finmind-backend -- alembic upgrade head',
deps=['deploy/k8s/app-stack.yaml'],
labels=['ops'],
)
12 changes: 12 additions & 0 deletions deploy/helm/finmind/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v2
name: finmind
description: FinMind β€” personal finance app Helm chart
type: application
version: 0.1.0
appVersion: "1.0.0"
keywords:
- finmind
- finance
- fastapi
maintainers:
- name: daatsuka
18 changes: 18 additions & 0 deletions deploy/helm/finmind/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "finmind.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "finmind.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
50 changes: 50 additions & 0 deletions deploy/helm/finmind/templates/deployment-backend.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "finmind.fullname" . }}-backend
labels:
app: {{ include "finmind.name" . }}-backend
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "finmind.name" . }}-backend
template:
metadata:
labels:
app: {{ include "finmind.name" . }}-backend
spec:
containers:
- name: backend
image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
ports:
- containerPort: {{ .Values.backend.port }}
envFrom:
- secretRef:
name: {{ include "finmind.fullname" . }}-secrets
livenessProbe:
httpGet:
path: /health
port: {{ .Values.backend.port }}
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /health
port: {{ .Values.backend.port }}
initialDelaySeconds: 5
periodSeconds: 10
resources:
{{- toYaml .Values.backend.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "finmind.fullname" . }}-backend
spec:
selector:
app: {{ include "finmind.name" . }}-backend
ports:
- port: 80
targetPort: {{ .Values.backend.port }}
20 changes: 20 additions & 0 deletions deploy/helm/finmind/templates/hpa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "finmind.fullname" . }}-backend
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "finmind.fullname" . }}-backend
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
29 changes: 29 additions & 0 deletions deploy/helm/finmind/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "finmind.fullname" . }}
annotations:
{{- toYaml .Values.ingress.annotations | nindent 4 }}
spec:
ingressClassName: {{ .Values.ingress.className }}
{{- if .Values.ingress.tls }}
tls:
{{- toYaml .Values.ingress.tls | nindent 4 }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "finmind.fullname" $ }}-{{ .service }}
port:
number: 80
{{- end }}
{{- end }}
{{- end }}
70 changes: 70 additions & 0 deletions deploy/helm/finmind/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
replicaCount: 1

backend:
image:
repository: finmind-backend
tag: latest
pullPolicy: IfNotPresent
port: 8000
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi

frontend:
image:
repository: finmind-frontend
tag: latest
pullPolicy: IfNotPresent
port: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi

autoscaling:
enabled: true
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 70

ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: finmind.example.com
paths:
- path: /api
pathType: Prefix
service: backend
- path: /
pathType: Prefix
service: frontend
tls:
- secretName: finmind-tls
hosts:
- finmind.example.com

postgresql:
enabled: true
auth:
username: finmind
password: ""
database: finmind
primary:
persistence:
size: 5Gi

redis:
enabled: true
architecture: standalone
auth:
enabled: false
84 changes: 84 additions & 0 deletions deploy/one-click.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# FinMind β€” Universal One-Click Deployment Script
# Usage: ./deploy/one-click.sh [platform]
# Platforms: docker | k8s | helm | railway | render | fly | heroku | do
set -euo pipefail

PLATFORM="${1:-docker}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"

info() { echo -e "\033[1;34m[FinMind]\033[0m $*"; }
ok() { echo -e "\033[1;32m[OK]\033[0m $*"; }
error() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; exit 1; }

case "$PLATFORM" in
docker)
info "Starting FinMind with Docker Compose..."
cd "$ROOT"
cp -n .env.example .env 2>/dev/null || true
docker compose up -d --build
ok "FinMind is running at http://localhost:3000 (frontend) and http://localhost:8000 (backend)"
;;

k8s)
info "Deploying to Kubernetes..."
command -v kubectl &>/dev/null || error "kubectl not found"
kubectl apply -f "$ROOT/deploy/k8s/namespace.yaml"
kubectl apply -f "$ROOT/deploy/k8s/app-stack.yaml"
kubectl apply -f "$ROOT/deploy/k8s/monitoring-stack.yaml"
kubectl rollout status deployment/finmind-backend -n finmind
ok "Deployed to Kubernetes"
;;

helm)
info "Deploying with Helm..."
command -v helm &>/dev/null || error "helm not found"
helm upgrade --install finmind "$ROOT/deploy/helm/finmind" \
--namespace finmind --create-namespace \
--wait --timeout 5m
ok "Helm release 'finmind' deployed"
;;

tilt)
info "Starting Tilt dev environment..."
command -v tilt &>/dev/null || error "tilt not found (https://tilt.dev)"
cd "$ROOT" && tilt up
;;

railway)
info "Deploying to Railway..."
command -v railway &>/dev/null || error "railway CLI not found"
cd "$ROOT" && railway up
;;

render)
info "Render deployment β€” push to git and Render auto-deploys from render.yaml"
ok "See deploy/platforms/render.yaml for config"
;;

fly)
info "Deploying to Fly.io..."
command -v fly &>/dev/null || error "fly CLI not found"
cd "$ROOT" && fly deploy --config deploy/platforms/fly.toml
;;

heroku)
info "Deploying to Heroku..."
command -v heroku &>/dev/null || error "heroku CLI not found"
heroku container:push web -a "${HEROKU_APP_NAME:-finmind}"
heroku container:release web -a "${HEROKU_APP_NAME:-finmind}"
ok "Deployed to Heroku"
;;

do)
info "Deploying to DigitalOcean App Platform..."
command -v doctl &>/dev/null || error "doctl not found"
doctl apps create --spec deploy/platforms/do-app.yaml
ok "DigitalOcean App created"
;;

*)
echo "Usage: $0 [docker|k8s|helm|tilt|railway|render|fly|heroku|do]"
exit 1
;;
esac
40 changes: 40 additions & 0 deletions deploy/platforms/do-app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: finmind
region: nyc
services:
- name: backend
dockerfile_path: packages/backend/Dockerfile
source_dir: packages/backend
http_port: 8000
instance_count: 1
instance_size_slug: basic-xxs
health_check:
http_path: /health
envs:
- key: DATABASE_URL
scope: RUN_TIME
type: SECRET
- key: REDIS_URL
scope: RUN_TIME
type: SECRET

- name: frontend
dockerfile_path: app/Dockerfile
source_dir: app
http_port: 80
instance_count: 1
instance_size_slug: basic-xxs
envs:
- key: VITE_API_URL
scope: RUN_TIME
value: ${backend.PUBLIC_URL}

databases:
- name: finmind-db
engine: PG
version: "16"
size: db-s-dev-database

- name: finmind-redis
engine: REDIS
version: "7"
size: db-s-dev-database
Loading