Compare commits

..
1 Commits
Author SHA1 Message Date
argoyleandClaude Opus 5.5 d041432772 feat: deploy to Frostmoln production
schemas / vulnerabilities (pull_request) Successful in 38s
schemas / check (pull_request) Successful in 1m8s
schemas / check-release (pull_request) Successful in 1m7s
pre-commit / pre-commit (pull_request) Successful in 1m17s
schemas / build (pull_request) Successful in 1m36s
schemas / deploy-prod-frostmoln (pull_request) Skipped
The AWS deploy-prod job has failed since CI left AWS (no k8s.unbound.se
context on the runners). Deploys now go to the Frostmoln production
cluster (namespace schemas), the way the sites do.

Frostmoln enforces restricted Pod Security: the pod runs as the image's
non-root user with a matching securityContext. The prod-only manifests
move to the prod-frostmoln target; the ALB ingress becomes a Traefik one
with a Let's Encrypt certificate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ma8eHhW59hLAJuLzBnLvUu
2026-10-14 22:15:38 +02:00
9 changed files with 69 additions and 53 deletions

No files matched your search

+18 -4
View File
@@ -83,16 +83,30 @@ jobs:
- name: Build and push
run: unset GITEA_TOKEN && build && push
deploy-prod:
deploy-prod-frostmoln:
needs: build
if: gitea.ref == 'refs/heads/main'
runs-on: ubuntu-latest
env:
BUILDTOOLS_CONTENT: ${{ secrets.BUILDTOOLS_CONTENT }}
BUILDTOOLS_CONTENT: ${{ secrets.BUILDTOOLS_FROSTMOLN_PRODUCTION }}
GITEA_REPOSITORY: ${{ gitea.repository }}
FM_TENANT: 9ae61342-0f76-476f-8176-e46f6b0b0c32
environment: prod
steps:
- uses: actions/checkout@v7
- uses: buildtool/setup-buildtools-action@v1
- name: Deploy to production
run: deploy prod
- name: Install fm
run: |
sudo install -d -m 0755 /etc/apt/keyrings
sudo curl -fsSL -o /etc/apt/keyrings/frostmoln.gpg https://get.frostmoln.cloud/deb/frostmoln.gpg
sudo curl -fsSL -o /etc/apt/sources.list.d/frostmoln.sources https://get.frostmoln.cloud/deb/frostmoln.sources
sudo apt-get update && sudo apt-get install -y fm
- name: Deploy to Frostmoln production
env:
FROSTMOLN_API_KEY: ${{ secrets.FROSTMOLN_PRODUCTION_DEPLOY_KEY }}
# The cluster API is private: the kubeconfig's exec plugin opens an fm
# tunnel on demand. fm reads the key from its config file, not the env.
run: |
fm auth api-key set "$FROSTMOLN_API_KEY" > /dev/null
fm kubernetes cluster kubeconfig ${{ vars.FROSTMOLN_PRODUCTION_CLUSTER_ID }} --exec --file "$RUNNER_TEMP/kubeconfig"
KUBECONFIG_CONTENT="$(cat "$RUNNER_TEMP/kubeconfig")" deploy prod-frostmoln -n schemas
+5 -1
View File
@@ -24,7 +24,7 @@ RUN GOOS=linux GOARCH=amd64 go build \
FROM scratch as export
COPY --from=build /build/coverage.txt /
FROM node:24.22.0-alpine@sha256:44fce696f38ba8c987c4db89954051000bbd05ffd8d8659ba15ed4a8602e937f
FROM node:24.21.0-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1
ENV TZ Europe/Stockholm
# Install wgc CLI globally for Cosmo Router composition
@@ -42,4 +42,8 @@ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy the service binary
COPY --from=build /release/service /
# The image's own non-root user: Frostmoln enforces restricted Pod Security.
# The service only writes under /tmp (cosmo composition).
USER node
CMD ["/service"]
File renamed without changes.
+9
View File
@@ -26,6 +26,11 @@ spec:
labels:
app.kubernetes.io/name: schemas
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
@@ -40,6 +45,10 @@ spec:
topologyKey: kubernetes.io/hostname
containers:
- name: schemas
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: "100m"
+20
View File
@@ -0,0 +1,20 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: schemas
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec:
ingressClassName: traefik
rules:
- host: "schemas.unbound.se"
http:
paths:
- path: /query
pathType: Prefix
backend:
service:
name: schemas
port:
name: api
-24
View File
@@ -1,24 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: schemas-ingress
annotations:
alb.ingress.kubernetes.io/group.name: "default"
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: instance
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80},{"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/healthcheck-path: '/health'
spec:
ingressClassName: "alb"
rules:
- host: "schemas.unbound.se"
http:
paths:
- path: /query
pathType: ImplementationSpecific
backend:
service:
name: schemas
port:
name: api
File renamed without changes.
+17
View File
@@ -0,0 +1,17 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: schemas
spec:
refreshInterval: 1h
# The namespace's store (infra frostmoln/production/schemas.tf); the
# Frostmoln Secret holds POSTGRES_URL, AMQP_URL and API_KEY
# (infra frostmoln/production/scripts/seed-schemas.sh).
secretStoreRef:
name: frostmoln
kind: SecretStore
target:
name: schemas
dataFrom:
- extract:
key: schemas
-24
View File
@@ -1,24 +0,0 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: schemas
spec:
refreshInterval: 1h
secretStoreRef:
name: external-secrets
kind: ClusterSecretStore
target:
creationPolicy: Owner
template:
mergePolicy: Merge
engineVersion: 'v2'
data:
POSTGRES_URL: "postgres://{{ .DB_USERNAME }}:{{ .DB_PASSWORD }}@{{ .DB_HOST }}:{{ .DB_PORT }}/schemas?sslmode=disable"
API_KEY: "{{ .API_KEY }}"
dataFrom:
- extract:
key: services/schemas
- extract:
key: rds/postgres/prod-psql
- extract:
key: mq/rabbit/prod