From 1be54637a6d580ad408e89a40182b316ed52e04c Mon Sep 17 00:00:00 2001 From: prateekmall Date: Mon, 10 Aug 2026 16:02:14 +0530 Subject: [PATCH 1/4] feat: implement secure container supply chain --- .github/workflows/devsecops.yml | 100 +++++++++++++++-------------- .github/workflows/docker-push.yml | 96 +++++++++++++++++++-------- .github/workflows/docker-scans.yml | 85 +++++++++++++----------- 3 files changed, 167 insertions(+), 114 deletions(-) diff --git a/.github/workflows/devsecops.yml b/.github/workflows/devsecops.yml index 15c4a12d..730b41fe 100644 --- a/.github/workflows/devsecops.yml +++ b/.github/workflows/devsecops.yml @@ -1,53 +1,59 @@ -# Goal - To Run End to End DevSecOps CI Pipeline - name: DevSecOps on: - push: - branches: [master] + push: + branches: + - master + - feat/dora-sota + + pull_request: + branches: + - master + +permissions: + contents: read + id-token: write jobs: - ## ---- CI ---- - # Code Stage - code-quality: - uses: ./.github/workflows/code-quality.yml - - # Secret Scanning - secret-scanning: - uses: ./.github/workflows/secret-scanning.yml - - # Dependency Checks - dependency-checks: - uses: ./.github/workflows/dependency-scan.yml - - # Docker Checks - docker-checks: - uses: ./.github/workflows/docker-scans.yml - secrets: inherit - - # SonarQube - sonar-qube: - uses: ./.github/workflows/sonar-scan.yml - secrets: inherit - - # Code Tests - code-tests: - uses: ./.github/workflows/code-tests.yml - - # push-to-docker-hub: - docker-push: - uses: ./.github/workflows/docker-push.yml - needs: [code-quality,code-tests,sonar-qube,docker-checks,dependency-checks,secret-scanning] - secrets: inherit - - ## ---- CD ----- - deploy: - needs: [docker-push] - uses: ./.github/workflows/deploy.yml - secrets: inherit - - dast-scan: - needs: [deploy] - uses: ./.github/workflows/dast.yml - secrets: inherit \ No newline at end of file + # ---------------- CI ---------------- + + code-quality: + uses: ./.github/workflows/code-quality.yml + + secret-scanning: + uses: ./.github/workflows/secret-scanning.yml + + dependency-checks: + uses: ./.github/workflows/dependency-scan.yml + + docker-checks: + uses: ./.github/workflows/docker-scans.yml + + sonar-qube: + uses: ./.github/workflows/sonar-scan.yml + secrets: inherit + + code-tests: + uses: ./.github/workflows/code-tests.yml + + # -------- Container Release -------- + + docker-build-scan-push: + uses: ./.github/workflows/docker-push.yml + needs: + - code-quality + - code-tests + - sonar-qube + - docker-checks + - dependency-checks + - secret-scanning + secrets: inherit + + # ---------------- CD ---------------- + # + # Direct deployment is intentionally disabled. + # + # GitHub Actions will later update the GitOps + # manifests and Argo CD will deploy them. + # \ No newline at end of file diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index c46f88fe..01aa18b3 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -1,31 +1,71 @@ -# Goal Push the images to DockerHub -name: Docker Push +name: Docker Build, Scan and Push -on: - workflow_call: +on: + workflow_call: jobs: - frontend: - # Github runner - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - folders: ['backend','frontend'] - steps: - - name: Checkout Code - uses: actions/checkout@v7 - - - name: Docker Setup [Login] - uses: docker/login-action@v4 - with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Docker Build and Push - uses: docker/build-push-action@v7 - with: - context: ./${{ matrix.folders}} - push: true - tags: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.folders}}:latest - \ No newline at end of file + build-scan-push: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + service: + - backend + - frontend + + permissions: + contents: read + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Docker Hub login + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build image + env: + IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} + run: | + docker build \ + -t "$IMAGE" \ + "./${{ matrix.service }}" + + - name: Trivy vulnerability scan + uses: aquasecurity/trivy-action@v0.36.0 + with: + image-ref: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} + format: table + exit-code: '1' + ignore-unfixed: true + vuln-type: os,library + severity: HIGH,CRITICAL + + - name: Generate SBOM + uses: anchore/sbom-action@v0 + with: + image: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} + format: spdx-json + artifact-name: ${{ matrix.service }}-sbom.spdx.json + output-file: ${{ matrix.service }}-sbom.spdx.json + upload-artifact: true + + - name: Push SHA-tagged image + env: + IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} + run: | + docker push "$IMAGE" + + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + + - name: Sign image + env: + IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} + run: | + cosign sign --yes "$IMAGE" \ No newline at end of file diff --git a/.github/workflows/docker-scans.yml b/.github/workflows/docker-scans.yml index 5ad07018..ef09cde9 100644 --- a/.github/workflows/docker-scans.yml +++ b/.github/workflows/docker-scans.yml @@ -1,44 +1,51 @@ # Goal - Ensure the docker files and Docker images are security Tested -name: Docker Scan +name: Docker Security Scan -on: - workflow_call: +on: + workflow_call: jobs: - docker-file-lint-and-scan: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - folders: ['backend','frontend'] - steps: - - name: Code Checkout - uses: actions/checkout@v7 - - - name: ${{ matrix.folders}} Dockerfile Lint - uses: hadolint/hadolint-action@v3.1.0 - with: - dockerfile: ${{ matrix.folders}}/Dockerfile - - - name: Docker Setup [Login] - uses: docker/login-action@v4 - with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Docker Build ${{ matrix.folders}} - run: docker build -t ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.folders }}:latest . - working-directory: ${{ matrix.folders}} - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@v0.36.0 - continue-on-error: true - with: - image-ref: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.folders }}:latest - format: 'table' - exit-code: '1' - ignore-unfixed: true - vuln-type: 'os,library' - severity: 'CRITICAL' - + docker-security: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + service: + - backend + - frontend + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Dockerfile lint + uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: ${{ matrix.service }}/Dockerfile + + - name: Build image + run: | + docker build \ + -t devboard-${{ matrix.service }}:${{ github.sha }} \ + ./${{ matrix.service }} + + - name: Trivy image scan + uses: aquasecurity/trivy-action@v0.36.0 + with: + image-ref: devboard-${{ matrix.service }}:${{ github.sha }} + format: table + exit-code: '1' + ignore-unfixed: true + vuln-type: os,library + severity: HIGH,CRITICAL + + - name: Generate SBOM + uses: anchore/sbom-action@v0 + with: + image: devboard-${{ matrix.service }}:${{ github.sha }} + format: spdx-json + artifact-name: ${{ matrix.service }}-sbom.spdx.json + output-file: ${{ matrix.service }}-sbom.spdx.json + upload-artifact: true \ No newline at end of file From 1eb9fbdcb375c5525118f9fe726f552a1553a08e Mon Sep 17 00:00:00 2001 From: prateekmall Date: Mon, 10 Aug 2026 20:35:22 +0530 Subject: [PATCH 2/4] feat: harden CI supply chain --- .github/workflows/devsecops.yml | 26 ++++++++------------ .github/workflows/docker-push.yml | 6 ++--- .github/workflows/docker-scans.yml | 39 ++++++------------------------ .github/workflows/sonar-scan.yml | 32 ++++++++++++++---------- 4 files changed, 39 insertions(+), 64 deletions(-) diff --git a/.github/workflows/devsecops.yml b/.github/workflows/devsecops.yml index 730b41fe..03c308d0 100644 --- a/.github/workflows/devsecops.yml +++ b/.github/workflows/devsecops.yml @@ -12,12 +12,9 @@ on: permissions: contents: read - id-token: write jobs: - # ---------------- CI ---------------- - code-quality: uses: ./.github/workflows/code-quality.yml @@ -32,15 +29,15 @@ jobs: sonar-qube: uses: ./.github/workflows/sonar-scan.yml - secrets: inherit + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} code-tests: uses: ./.github/workflows/code-tests.yml - # -------- Container Release -------- - docker-build-scan-push: - uses: ./.github/workflows/docker-push.yml + if: github.event_name == 'push' && github.ref == 'refs/heads/master' needs: - code-quality - code-tests @@ -48,12 +45,9 @@ jobs: - docker-checks - dependency-checks - secret-scanning - secrets: inherit - - # ---------------- CD ---------------- - # - # Direct deployment is intentionally disabled. - # - # GitHub Actions will later update the GitOps - # manifests and Argo CD will deploy them. - # \ No newline at end of file + permissions: + contents: read + id-token: write + uses: ./.github/workflows/docker-push.yml + secrets: + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 01aa18b3..4a65abe9 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -37,7 +37,7 @@ jobs: "./${{ matrix.service }}" - name: Trivy vulnerability scan - uses: aquasecurity/trivy-action@v0.36.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 with: image-ref: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} format: table @@ -47,7 +47,7 @@ jobs: severity: HIGH,CRITICAL - name: Generate SBOM - uses: anchore/sbom-action@v0 + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 with: image: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} format: spdx-json @@ -62,7 +62,7 @@ jobs: docker push "$IMAGE" - name: Install Cosign - uses: sigstore/cosign-installer@v3 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 - name: Sign image env: diff --git a/.github/workflows/docker-scans.yml b/.github/workflows/docker-scans.yml index ef09cde9..73140d2a 100644 --- a/.github/workflows/docker-scans.yml +++ b/.github/workflows/docker-scans.yml @@ -1,12 +1,10 @@ -# Goal - Ensure the docker files and Docker images are security Tested - -name: Docker Security Scan +name: Dockerfile Security Scan on: workflow_call: jobs: - docker-security: + dockerfile-security: runs-on: ubuntu-latest strategy: @@ -18,34 +16,11 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v7 - - - name: Dockerfile lint - uses: hadolint/hadolint-action@v3.1.0 + uses: actions/checkout@v4 with: - dockerfile: ${{ matrix.service }}/Dockerfile - - - name: Build image - run: | - docker build \ - -t devboard-${{ matrix.service }}:${{ github.sha }} \ - ./${{ matrix.service }} + persist-credentials: false - - name: Trivy image scan - uses: aquasecurity/trivy-action@v0.36.0 - with: - image-ref: devboard-${{ matrix.service }}:${{ github.sha }} - format: table - exit-code: '1' - ignore-unfixed: true - vuln-type: os,library - severity: HIGH,CRITICAL - - - name: Generate SBOM - uses: anchore/sbom-action@v0 + - name: Dockerfile lint + uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf with: - image: devboard-${{ matrix.service }}:${{ github.sha }} - format: spdx-json - artifact-name: ${{ matrix.service }}-sbom.spdx.json - output-file: ${{ matrix.service }}-sbom.spdx.json - upload-artifact: true \ No newline at end of file + dockerfile: ${{ matrix.service }}/Dockerfile \ No newline at end of file diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index e15dadf3..2c9391fd 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -1,19 +1,25 @@ -# Goal - to check whether the application code coverage doesn't have security threats/issues - name: Sonar Qube Scan on: - workflow_call: + workflow_call: + secrets: + SONAR_TOKEN: + required: true + SONAR_HOST_URL: + required: true jobs: - sonar-scanner: - runs-on: ubuntu-latest - steps: - - name: Code Checkout - uses: actions/checkout@v7 + sonar-scanner: + runs-on: ubuntu-latest + + steps: + - name: Code Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false - - name: SonarQube Scan - uses: SonarSource/sonarqube-scan-action@v8.2.0 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} \ No newline at end of file + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} \ No newline at end of file From a94b8c8c0a8d3803b9a735069708586dafe47781 Mon Sep 17 00:00:00 2001 From: prateekmall Date: Wed, 12 Aug 2026 12:12:18 +0530 Subject: [PATCH 3/4] feat: add Helm chart for GitOps deployment --- helm/devboard/Chart.yaml | 6 ++ helm/devboard/templates/_helpers.tpl | 27 +++++++ .../templates/backend-deployment.yaml | 46 +++++++++++ helm/devboard/templates/backend-service.yaml | 12 +++ helm/devboard/templates/configmap.yaml | 7 ++ .../templates/frontend-deployment.yaml | 38 +++++++++ helm/devboard/templates/frontend-service.yaml | 14 ++++ helm/devboard/templates/postgres-init.yaml | 68 ++++++++++++++++ helm/devboard/templates/postgres-service.yaml | 13 +++ .../templates/postgres-statefulset.yaml | 81 +++++++++++++++++++ helm/devboard/templates/secret.yaml | 9 +++ helm/devboard/values.yaml | 57 +++++++++++++ 12 files changed, 378 insertions(+) create mode 100644 helm/devboard/Chart.yaml create mode 100644 helm/devboard/templates/_helpers.tpl create mode 100644 helm/devboard/templates/backend-deployment.yaml create mode 100644 helm/devboard/templates/backend-service.yaml create mode 100644 helm/devboard/templates/configmap.yaml create mode 100644 helm/devboard/templates/frontend-deployment.yaml create mode 100644 helm/devboard/templates/frontend-service.yaml create mode 100644 helm/devboard/templates/postgres-init.yaml create mode 100644 helm/devboard/templates/postgres-service.yaml create mode 100644 helm/devboard/templates/postgres-statefulset.yaml create mode 100644 helm/devboard/templates/secret.yaml create mode 100644 helm/devboard/values.yaml diff --git a/helm/devboard/Chart.yaml b/helm/devboard/Chart.yaml new file mode 100644 index 00000000..9ad98892 --- /dev/null +++ b/helm/devboard/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: devboard +description: DevBoard - React + Go + Postgres application +type: application +version: 0.1.0 +appVersion: "1.0.0" diff --git a/helm/devboard/templates/_helpers.tpl b/helm/devboard/templates/_helpers.tpl new file mode 100644 index 00000000..d4b86d43 --- /dev/null +++ b/helm/devboard/templates/_helpers.tpl @@ -0,0 +1,27 @@ +{{/* +Chart name (respects nameOverride). +*/}} +{{- define "devboard.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Fully qualified app name. +*/}} +{{- define "devboard.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name (include "devboard.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Common labels stamped on every resource. +*/}} +{{- define "devboard.labels" -}} +app.kubernetes.io/name: {{ include "devboard.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }} +{{- end -}} diff --git a/helm/devboard/templates/backend-deployment.yaml b/helm/devboard/templates/backend-deployment.yaml new file mode 100644 index 00000000..fd16d0b4 --- /dev/null +++ b/helm/devboard/templates/backend-deployment.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "devboard.fullname" . }}-backend + labels: + {{- include "devboard.labels" . | nindent 4 }} + app: devboard-backend +spec: + replicas: {{ .Values.backend.replicas }} + selector: + matchLabels: + app: devboard-backend + template: + metadata: + labels: + {{- include "devboard.labels" . | nindent 8 }} + app: devboard-backend + spec: + containers: + - name: devboard-backend + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.backend.port }} + env: + - name: POSTGRES_URL + valueFrom: + secretKeyRef: + name: devboard-secrets + key: POSTGRES_URL + - name: PORT + value: {{ .Values.backend.port | quote }} + livenessProbe: + httpGet: + path: /health + port: {{ .Values.backend.port }} + initialDelaySeconds: 15 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: {{ .Values.backend.port }} + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} diff --git a/helm/devboard/templates/backend-service.yaml b/helm/devboard/templates/backend-service.yaml new file mode 100644 index 00000000..d10bf1f1 --- /dev/null +++ b/helm/devboard/templates/backend-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.backend.serviceName }} + namespace: devboard +spec: + selector: + app: devboard-backend + ports: + - protocol: TCP + port: {{ .Values.backend.port }} + targetPort: {{ .Values.backend.port }} diff --git a/helm/devboard/templates/configmap.yaml b/helm/devboard/templates/configmap.yaml new file mode 100644 index 00000000..a09d45aa --- /dev/null +++ b/helm/devboard/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: devboard-configmap + namespace: devboard +data: + POSTGRES_USER: {{ .Values.postgres.user | quote }} diff --git a/helm/devboard/templates/frontend-deployment.yaml b/helm/devboard/templates/frontend-deployment.yaml new file mode 100644 index 00000000..48266107 --- /dev/null +++ b/helm/devboard/templates/frontend-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "devboard.fullname" . }}-frontend + labels: + {{- include "devboard.labels" . | nindent 4 }} + app: devboard-frontend +spec: + replicas: {{ .Values.frontend.replicas }} + selector: + matchLabels: + app: devboard-frontend + template: + metadata: + labels: + {{- include "devboard.labels" . | nindent 8 }} + app: devboard-frontend + spec: + containers: + - name: devboard-frontend + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.containerPort }} + livenessProbe: + httpGet: + path: / + port: {{ .Values.frontend.containerPort }} + initialDelaySeconds: 15 + periodSeconds: 15 + readinessProbe: + httpGet: + path: / + port: {{ .Values.frontend.containerPort }} + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} diff --git a/helm/devboard/templates/frontend-service.yaml b/helm/devboard/templates/frontend-service.yaml new file mode 100644 index 00000000..9aaa16a3 --- /dev/null +++ b/helm/devboard/templates/frontend-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: devboard-frontend-service + namespace: devboard +spec: + selector: + app: devboard-frontend + ports: + - protocol: TCP + port: {{ .Values.frontend.servicePort }} + targetPort: {{ .Values.frontend.containerPort }} + nodePort: 30080 + type: NodePort diff --git a/helm/devboard/templates/postgres-init.yaml b/helm/devboard/templates/postgres-init.yaml new file mode 100644 index 00000000..9c1bead0 --- /dev/null +++ b/helm/devboard/templates/postgres-init.yaml @@ -0,0 +1,68 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-init + namespace: devboard +data: + 01_schema.sql: | + CREATE TABLE IF NOT EXISTS projects ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + description TEXT, + owner_id INTEGER, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + + CREATE TABLE IF NOT EXISTS tasks ( + id SERIAL PRIMARY KEY, + title VARCHAR(300) NOT NULL, + description TEXT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + assignee_id INTEGER, + status VARCHAR(20) NOT NULL DEFAULT 'todo' + CHECK (status IN ('todo','in_progress','blocked','done')), + priority VARCHAR(10) NOT NULL DEFAULT 'medium' + CHECK (priority IN ('low','medium','high')), + due_date DATE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ); + + CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id); + CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status); + + CREATE OR REPLACE FUNCTION touch_updated_at() RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = NOW(); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS tasks_touch_updated_at ON tasks; + + CREATE TRIGGER tasks_touch_updated_at + BEFORE UPDATE ON tasks + FOR EACH ROW + EXECUTE FUNCTION touch_updated_at(); + + 02_seed.sql: | + INSERT INTO projects (id, name, description, owner_id) VALUES + (1, 'DevBoard MVP', 'Ship the v1 task tracker', 1), + (2, 'Marketing Site', 'Landing page + launch blog', 1) + ON CONFLICT (id) DO NOTHING; + + INSERT INTO tasks + (title, description, project_id, assignee_id, status, priority, due_date) + VALUES + ('Design the task schema', 'projects, tasks, statuses', 1, 1, 'done', 'high', '2026-05-05'), + ('Build the kanban board', 'drag and drop columns', 1, 2, 'in_progress', 'high', '2026-06-18'), + ('Wire up the dashboard hero', 'velocity + chip stats', 1, 1, 'in_progress', 'medium', '2026-06-20'), + ('Add the command bar', 'global search', 1, 3, 'todo', 'medium', '2026-06-25'), + ('Dark mode polish', 'grain + gradients', 1, 2, 'todo', 'low', NULL), + ('Fix flaky avatar colors', 'hash collision on initials', 1, 1, 'blocked', 'high', '2026-06-15'), + ('Write component tests', 'Vitest + testing-library', 1, 3, 'todo', 'medium', NULL), + ('Ship the v1 release', 'tag and announce', 1, 1, 'todo', 'high', '2026-06-30'), + ('Draft the launch blog post', '', 2, 2, 'in_progress', 'medium', '2026-06-22'), + ('Hero illustration', '', 2, 3, 'todo', 'low', NULL); + + SELECT setval('projects_id_seq', (SELECT MAX(id) FROM projects)); diff --git a/helm/devboard/templates/postgres-service.yaml b/helm/devboard/templates/postgres-service.yaml new file mode 100644 index 00000000..8ffbe03b --- /dev/null +++ b/helm/devboard/templates/postgres-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.postgres.serviceName }} + namespace: devboard +spec: + clusterIP: None + selector: + app: devboard-postgres + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 diff --git a/helm/devboard/templates/postgres-statefulset.yaml b/helm/devboard/templates/postgres-statefulset.yaml new file mode 100644 index 00000000..5cef415c --- /dev/null +++ b/helm/devboard/templates/postgres-statefulset.yaml @@ -0,0 +1,81 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgres-statefulset + namespace: devboard + labels: + app: devboard-postgres + +spec: + serviceName: {{ .Values.postgres.serviceName }} + replicas: 1 + + selector: + matchLabels: + app: devboard-postgres + + template: + metadata: + labels: + app: devboard-postgres + + spec: + containers: + - name: postgres + image: {{ .Values.postgres.image }} + ports: + - containerPort: 5432 + + env: + - name: POSTGRES_USER + valueFrom: + configMapKeyRef: + name: devboard-configmap + key: POSTGRES_USER + + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: devboard-secrets + key: POSTGRES_PASSWORD + + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: devboard-secrets + key: POSTGRES_DB + + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + + readinessProbe: + exec: + command: + - sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 15 + periodSeconds: 10 + + volumeMounts: + - name: init + mountPath: /docker-entrypoint-initdb.d + readOnly: true + + - name: data + mountPath: /var/lib/postgresql/data + + volumes: + - name: init + configMap: + name: postgres-init + + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.postgres.storage.size }} diff --git a/helm/devboard/templates/secret.yaml b/helm/devboard/templates/secret.yaml new file mode 100644 index 00000000..10031cbe --- /dev/null +++ b/helm/devboard/templates/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: devboard-secrets + namespace: devboard +type: Opaque +stringData: + POSTGRES_PASSWORD: {{ .Values.postgres.password | quote }} + POSTGRES_DB: {{ .Values.postgres.db | quote }} diff --git a/helm/devboard/values.yaml b/helm/devboard/values.yaml new file mode 100644 index 00000000..f95884cb --- /dev/null +++ b/helm/devboard/values.yaml @@ -0,0 +1,57 @@ +nameOverride: "" +fullnameOverride: "" + +postgres: + image: postgres:16-alpine + user: devboard + password: devboard + db: devboard + serviceName: postgres + storage: + size: 1Gi + storageClassName: "" + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + +backend: + image: + repository: trainwithshubham/devboard-backend + tag: latest + pullPolicy: IfNotPresent + replicas: 1 + serviceName: backend + port: 8080 + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + +frontend: + image: + repository: trainwithshubham/devboard-frontend + tag: latest + pullPolicy: IfNotPresent + replicas: 1 + containerPort: 4173 + servicePort: 8080 + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + +hpa: + enabled: true + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 60 From afd62a5bf76b523fa51e6227d205967dc68f45d2 Mon Sep 17 00:00:00 2001 From: prateekmall Date: Thu, 13 Aug 2026 12:25:16 +0530 Subject: [PATCH 4/4] feat: automate GitOps image bump --- .github/workflows/docker-push.yml | 14 ++++++- .github/workflows/gitops-bump.yml | 67 +++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/gitops-bump.yml diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 4a65abe9..5242484e 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -2,7 +2,9 @@ name: Docker Build, Scan and Push on: workflow_call: - + secrets: + DOCKERHUB_TOKEN: + required: true jobs: build-scan-push: runs-on: ubuntu-latest @@ -68,4 +70,12 @@ jobs: env: IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/devboard-${{ matrix.service }}:${{ github.sha }} run: | - cosign sign --yes "$IMAGE" \ No newline at end of file + cosign sign --yes "$IMAGE" + + gitops-bump: + needs: build-scan-push + permissions: + contents: write + uses: ./.github/workflows/gitops-bump.yml + with: + image_tag: ${{ github.sha }} \ No newline at end of file diff --git a/.github/workflows/gitops-bump.yml b/.github/workflows/gitops-bump.yml new file mode 100644 index 00000000..a6ba8e82 --- /dev/null +++ b/.github/workflows/gitops-bump.yml @@ -0,0 +1,67 @@ +# GitOps handoff: write the freshly-built image tag into the manifests and +# commit it back to `gitops`. ArgoCD (watching that branch) then syncs. +# +# The commit touches only k8s/ + helm/, which are NOT in the pipeline's push +# `paths:` filter, and commits made with GITHUB_TOKEN don't trigger workflows — +# so this can't cause a rebuild loop ([skip ci] is a third safety net). +name: GitOps Image Bump + +on: + workflow_call: + inputs: + image_tag: + required: true + type: string + +jobs: + bump: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout gitops + uses: actions/checkout@v7 + with: + ref: gitops + + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Write image refs into manifests + env: + OWNER: ${{ vars.DOCKERHUB_USERNAME }} + TAG: ${{ inputs.image_tag }} + run: | + set -eu + # RAW manifests (if present): surgical sed on the single "image:" line, + # keeping the file's formatting/comments untouched. + for svc in backend frontend; do + f="k8s/${svc}-deployment.yml" + [ -f "$f" ] && sed -i "s|image: .*devboard-${svc}:.*|image: ${OWNER}/devboard-${svc}:${TAG}|" "$f" + done + # HELM values (if present): structured repository/tag — owner written + # too, so it can't drift from what CI pushed. + v="helm/devboard/values.yaml" + if [ -f "$v" ]; then + yq -i ".backend.image.repository = \"${OWNER}/devboard-backend\" | .backend.image.tag = \"${TAG}\"" "$v" + yq -i ".frontend.image.repository = \"${OWNER}/devboard-frontend\" | .frontend.image.tag = \"${TAG}\"" "$v" + fi + + - name: Commit and push + env: + TAG: ${{ inputs.image_tag }} + run: | + set -eu + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if git diff --quiet; then + echo "Manifests already at ${TAG}; nothing to commit." + exit 0 + fi + git add -A + git commit -m "ci: deploy ${TAG} [skip ci]" + git push origin HEAD:gitops