Add Kubernetes image + Helm package with in-cluster Postgres #1

Merged
fez5587 merged 3 commits from claude/kubernetes-image-package-kkdkom into master 2026-06-29 17:47:26 -04:00
fez5587 commented 2026-06-19 18:38:33 -04:00 (Migrated from github.com)

Containerizes the momentum monolith and packages every run mode for
deployment in a single Kubernetes namespace, with PostgreSQL hosted in
the cluster.

  • Dockerfile (python:3.11-slim, non-root, writable /app/data) + .dockerignore
  • GitHub Actions workflow publishing to ghcr.io/fez5587/momentum-system
  • Helm chart (deploy/helm/momentum):
    • PostgreSQL StatefulSet + PVC (single source of truth; schema
      auto-creates from storage/pg_schema.sql on first connect)
    • App Deployment running run_live_paper.py with the embedded dashboard
      (replicas: 1, Recreate; the dashboard's write endpoints need the
      in-process TradingExecutionService) + Service/Ingress on 8010, plus a
      wait-for-postgres initContainer
    • CronJobs for journal / eod-replay / nightly-tune (US/Eastern via
      spec.timeZone) sharing the app data PVC
    • ConfigMap (tuning env) + Secret (DATABASE_URL/broker keys, existingSecret
      supported); DATABASE_URL built from the in-cluster Postgres service
    • Optional Grafana provisioned against the in-cluster Postgres
  • deploy/README.md with install, secrets, and constraints

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01JCVMm2AbmZvvxwLTX6UpNR

Containerizes the momentum monolith and packages every run mode for deployment in a single Kubernetes namespace, with PostgreSQL hosted in the cluster. - Dockerfile (python:3.11-slim, non-root, writable /app/data) + .dockerignore - GitHub Actions workflow publishing to ghcr.io/fez5587/momentum-system - Helm chart (deploy/helm/momentum): * PostgreSQL StatefulSet + PVC (single source of truth; schema auto-creates from storage/pg_schema.sql on first connect) * App Deployment running run_live_paper.py with the embedded dashboard (replicas: 1, Recreate; the dashboard's write endpoints need the in-process TradingExecutionService) + Service/Ingress on 8010, plus a wait-for-postgres initContainer * CronJobs for journal / eod-replay / nightly-tune (US/Eastern via spec.timeZone) sharing the app data PVC * ConfigMap (tuning env) + Secret (DATABASE_URL/broker keys, existingSecret supported); DATABASE_URL built from the in-cluster Postgres service * Optional Grafana provisioned against the in-cluster Postgres - deploy/README.md with install, secrets, and constraints Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JCVMm2AbmZvvxwLTX6UpNR
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-06-29 17:46:14 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

This PR adds a container image and deployment packaging to run the Momentum monolith in Kubernetes (single namespace) with an in-cluster PostgreSQL, plus a local docker-compose stack intended to mirror the Helm setup.

Changes:

  • Added a Python 3.11 slim Docker image for the monolith (non-root, /app/data intended to be writable via volume/PVC).
  • Added a Helm chart (deploy/helm/momentum) that deploys Postgres (StatefulSet+PVC), the singleton app Deployment, CronJobs, optional Grafana, and supporting ConfigMap/Secret resources.
  • Added docker-compose and publishing workflow for building/pushing the image to GHCR.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
Dockerfile Builds a non-root container image for running the monolith in multiple “run modes”.
.dockerignore Excludes secrets/runtime data and deployment assets from the image build context.
docker-compose.yml Local stack: Postgres + app + optional Grafana, plus on-demand job containers.
.github/workflows/docker-publish.yml Builds and publishes the image to GHCR on push/tag.
deploy/README.md Kubernetes + Compose usage and operational notes/constraints.
deploy/helm/momentum/Chart.yaml New Helm chart metadata (incl. kubeVersion constraint).
deploy/helm/momentum/values.yaml Default values for image, app, Postgres, CronJobs, Grafana, config, secrets.
deploy/helm/momentum/templates/_helpers.tpl Naming helpers + Secret name selection + Postgres password resolution.
deploy/helm/momentum/templates/secret.yaml Renders chart-managed Secret (or supports existingSecret).
deploy/helm/momentum/templates/configmap.yaml Renders ConfigMap for non-secret tuning env.
deploy/helm/momentum/templates/app-deployment.yaml Singleton app Deployment + initContainer + probes + PVC mount.
deploy/helm/momentum/templates/app-service.yaml Service for the embedded dashboard.
deploy/helm/momentum/templates/ingress.yaml Optional Ingress for the app service.
deploy/helm/momentum/templates/app-data-pvc.yaml PVC for shared /app/data.
deploy/helm/momentum/templates/postgres-statefulset.yaml In-cluster Postgres StatefulSet + PVC template + probes.
deploy/helm/momentum/templates/postgres-service.yaml Headless service for Postgres discovery.
deploy/helm/momentum/templates/cronjobs.yaml CronJobs for journal/eod-replay/nightly-tune.
deploy/helm/momentum/templates/grafana.yaml Optional Grafana deployment with provisioning + PVC.
deploy/helm/momentum/templates/NOTES.txt Post-install hints and warnings.
deploy/helm/momentum/files/dashboard.json Grafana dashboard JSON embedded into the chart.
deploy/helm/momentum/.helmignore Basic Helm ignore patterns.
deploy/compose/grafana/datasources/momentum.yaml Compose-only Grafana datasource pointing at the compose Postgres service.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

## Pull request overview This PR adds a container image and deployment packaging to run the Momentum monolith in Kubernetes (single namespace) with an in-cluster PostgreSQL, plus a local docker-compose stack intended to mirror the Helm setup. **Changes:** - Added a Python 3.11 slim Docker image for the monolith (non-root, `/app/data` intended to be writable via volume/PVC). - Added a Helm chart (`deploy/helm/momentum`) that deploys Postgres (StatefulSet+PVC), the singleton app Deployment, CronJobs, optional Grafana, and supporting ConfigMap/Secret resources. - Added docker-compose and publishing workflow for building/pushing the image to GHCR. ### Reviewed changes Copilot reviewed 22 out of 22 changed files in this pull request and generated 8 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | Dockerfile | Builds a non-root container image for running the monolith in multiple “run modes”. | | .dockerignore | Excludes secrets/runtime data and deployment assets from the image build context. | | docker-compose.yml | Local stack: Postgres + app + optional Grafana, plus on-demand job containers. | | .github/workflows/docker-publish.yml | Builds and publishes the image to GHCR on push/tag. | | deploy/README.md | Kubernetes + Compose usage and operational notes/constraints. | | deploy/helm/momentum/Chart.yaml | New Helm chart metadata (incl. kubeVersion constraint). | | deploy/helm/momentum/values.yaml | Default values for image, app, Postgres, CronJobs, Grafana, config, secrets. | | deploy/helm/momentum/templates/_helpers.tpl | Naming helpers + Secret name selection + Postgres password resolution. | | deploy/helm/momentum/templates/secret.yaml | Renders chart-managed Secret (or supports existingSecret). | | deploy/helm/momentum/templates/configmap.yaml | Renders ConfigMap for non-secret tuning env. | | deploy/helm/momentum/templates/app-deployment.yaml | Singleton app Deployment + initContainer + probes + PVC mount. | | deploy/helm/momentum/templates/app-service.yaml | Service for the embedded dashboard. | | deploy/helm/momentum/templates/ingress.yaml | Optional Ingress for the app service. | | deploy/helm/momentum/templates/app-data-pvc.yaml | PVC for shared `/app/data`. | | deploy/helm/momentum/templates/postgres-statefulset.yaml | In-cluster Postgres StatefulSet + PVC template + probes. | | deploy/helm/momentum/templates/postgres-service.yaml | Headless service for Postgres discovery. | | deploy/helm/momentum/templates/cronjobs.yaml | CronJobs for journal/eod-replay/nightly-tune. | | deploy/helm/momentum/templates/grafana.yaml | Optional Grafana deployment with provisioning + PVC. | | deploy/helm/momentum/templates/NOTES.txt | Post-install hints and warnings. | | deploy/helm/momentum/files/dashboard.json | Grafana dashboard JSON embedded into the chart. | | deploy/helm/momentum/.helmignore | Basic Helm ignore patterns. | | deploy/compose/grafana/datasources/momentum.yaml | Compose-only Grafana datasource pointing at the compose Postgres service. | </details> --- 💡 <a href="/fez5587/momentum-system/new/master?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.
@@ -0,0 +7,4 @@
push:
branches:
- claude/kubernetes-image-package-kkdkom
- main
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:13 -04:00

The workflow is configured to build/publish on a specific feature branch name (claude/kubernetes-image-package-kkdkom). Keeping a one-off branch trigger in main will cause unexpected publishes if the branch is reused, and it’s easy to forget to remove later.

The workflow is configured to build/publish on a specific feature branch name (claude/kubernetes-image-package-kkdkom). Keeping a one-off branch trigger in main will cause unexpected publishes if the branch is reused, and it’s easy to forget to remove later.
@@ -0,0 +20,4 @@
{{- include "momentum.selectorLabels" (dict "ctx" . "component" "app") | nindent 8 }}
annotations:
# Roll the pod when config/secret content changes.
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:12 -04:00

The pod template annotation claims to roll on config/secret changes, but only the ConfigMap is hashed. As a result, updating the chart-managed Secret (e.g., rotating DB/broker keys) won’t trigger a rollout automatically.

The pod template annotation claims to roll on config/secret changes, but only the ConfigMap is hashed. As a result, updating the chart-managed Secret (e.g., rotating DB/broker keys) won’t trigger a rollout automatically.
@@ -0,0 +25,4 @@
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:13 -04:00

The container runs as a non-root user (per the Dockerfile), but the mounted PVC at /app/data will typically be owned by root on first mount. Without a pod securityContext (fsGroup / runAsUser), the app may fail to write logs/state into /app/data at runtime.

The container runs as a non-root user (per the Dockerfile), but the mounted PVC at /app/data will typically be owned by root on first mount. Without a pod securityContext (fsGroup / runAsUser), the app may fail to write logs/state into /app/data at runtime.
@@ -0,0 +1,59 @@
{{- if .Values.cronjobs.enabled }}
{{- $root := . }}
{{- $jobs := dict "journal" .Values.cronjobs.journal "eod-replay" .Values.cronjobs.eodReplay "nightly-tune" .Values.cronjobs.nightlyTune }}
{{- range $key, $job := $jobs }}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:14 -04:00

This template ranges over a dict of jobs. Go map iteration order is non-deterministic, so the rendered manifest order can change between Helm runs, creating noisy diffs (e.g. in helm template output / CI). Consider iterating a list (or rendering each CronJob explicitly) to keep output stable.

This template ranges over a dict of jobs. Go map iteration order is non-deterministic, so the rendered manifest order can change between Helm runs, creating noisy diffs (e.g. in `helm template` output / CI). Consider iterating a list (or rendering each CronJob explicitly) to keep output stable.
@@ -0,0 +30,4 @@
{{- with $root.Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 12 }}
{{- end }}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:13 -04:00

CronJob pods mount the shared /app/data PVC while running as a non-root user (from the image). Without setting fsGroup/runAsUser in the pod securityContext, these jobs may be unable to read/write learned_params.json, logs, tokens, etc. on many storage classes.

CronJob pods mount the shared /app/data PVC while running as a non-root user (from the image). Without setting fsGroup/runAsUser in the pod securityContext, these jobs may be unable to read/write learned_params.json, logs, tokens, etc. on many storage classes.
@@ -0,0 +78,4 @@
- name: GF_AUTH_ANONYMOUS_ORG_ROLE
value: "Admin"
- name: GF_AUTH_DISABLE_LOGIN_FORM
value: "true"
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:14 -04:00

The Grafana deployment enables anonymous access with Admin role and disables the login form. In a cluster this effectively grants admin access to anyone who can reach the Service (including other pods), which is unsafe by default.

The Grafana deployment enables anonymous access with Admin role and disables the login form. In a cluster this effectively grants admin access to anyone who can reach the Service (including other pods), which is unsafe by default.
@@ -0,0 +1,26 @@
{{- if not .Values.secrets.existingSecret }}
{{- $pgPassword := include "momentum.postgresPassword" . }}
{{- $pgHost := include "momentum.postgresName" . }}
{{- $databaseUrl := printf "postgresql://%s:%s@%s:%v/%s" .Values.postgres.username $pgPassword $pgHost .Values.postgres.port .Values.postgres.database }}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:13 -04:00

DATABASE_URL is constructed via string interpolation without URL-encoding. If a user supplies a Postgres password/username containing reserved URL characters (e.g. ':' or '@'), psycopg2 URL parsing will break and the app won’t be able to connect.

DATABASE_URL is constructed via string interpolation without URL-encoding. If a user supplies a Postgres password/username containing reserved URL characters (e.g. ':' or '@'), psycopg2 URL parsing will break and the app won’t be able to connect.
@@ -0,0 +146,4 @@
# ----------------------------------------------------------------------------
grafana:
enabled: true
image: grafana/grafana:11.2.0
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-06-29 17:46:14 -04:00

Grafana is described as optional, but it is enabled by default. Given the current template enables anonymous admin access, default-on Grafana is a risky default for in-cluster installs.

Grafana is described as optional, but it is enabled by default. Given the current template enables anonymous admin access, default-on Grafana is a risky default for in-cluster installs.
fez5587 (Migrated from github.com) reviewed 2026-06-29 17:47:17 -04:00
fez5587 (Migrated from github.com) left a comment

Ok

Ok
Sign in to join this conversation.