diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index c519b7c..7cd30de 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest if: "!startsWith(github.event.head_commit.message, 'chore: bump version')" env: - REGISTRY: git2.lunarfront.tech + REGISTRY: registry.digitalocean.com/lunarfront GIT_REMOTE: git2.lunarfront.tech DOCKER_HOST: tcp://localhost:2375 @@ -53,8 +53,8 @@ jobs: ") echo "version=$VERSION" >> $GITHUB_OUTPUT - - name: Login to registry - run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login $REGISTRY -u ryan --password-stdin + - name: Login to DOCR + run: echo "${{ secrets.DOCR_TOKEN }}" | docker login registry.digitalocean.com -u token --password-stdin - name: Build and push backend run: | @@ -62,26 +62,42 @@ jobs: SHA=$(git rev-parse --short HEAD) docker build \ --build-arg APP_VERSION=$VERSION \ - -t $REGISTRY/ryan/lunarfront-app:$VERSION \ - -t $REGISTRY/ryan/lunarfront-app:$SHA \ - -t $REGISTRY/ryan/lunarfront-app:latest \ + -t $REGISTRY/lunarfront-app:$VERSION \ + -t $REGISTRY/lunarfront-app:$SHA \ + -t $REGISTRY/lunarfront-app:latest \ -f Dockerfile . - docker push $REGISTRY/ryan/lunarfront-app:$VERSION - docker push $REGISTRY/ryan/lunarfront-app:$SHA - docker push $REGISTRY/ryan/lunarfront-app:latest + docker push $REGISTRY/lunarfront-app:$VERSION + docker push $REGISTRY/lunarfront-app:$SHA + docker push $REGISTRY/lunarfront-app:latest - name: Build and push frontend run: | VERSION=${{ steps.version.outputs.version }} SHA=$(git rev-parse --short HEAD) docker build \ - -t $REGISTRY/ryan/lunarfront-frontend:$VERSION \ - -t $REGISTRY/ryan/lunarfront-frontend:$SHA \ - -t $REGISTRY/ryan/lunarfront-frontend:latest \ + -t $REGISTRY/lunarfront-frontend:$VERSION \ + -t $REGISTRY/lunarfront-frontend:$SHA \ + -t $REGISTRY/lunarfront-frontend:latest \ -f Dockerfile.frontend . - docker push $REGISTRY/ryan/lunarfront-frontend:$VERSION - docker push $REGISTRY/ryan/lunarfront-frontend:$SHA - docker push $REGISTRY/ryan/lunarfront-frontend:latest + docker push $REGISTRY/lunarfront-frontend:$VERSION + docker push $REGISTRY/lunarfront-frontend:$SHA + docker push $REGISTRY/lunarfront-frontend:latest + + - name: Install Helm + run: | + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + + - name: Package and push Helm chart + run: | + VERSION=${{ steps.version.outputs.version }} + # Update chart version to match app version + sed -i "s/^version:.*/version: $VERSION/" chart/Chart.yaml + sed -i "s/^appVersion:.*/appVersion: \"$VERSION\"/" chart/Chart.yaml + # Update default image tags in values.yaml to this version + sed -i "s|tag: latest|tag: $VERSION|g" chart/values.yaml + helm registry login registry.digitalocean.com -u token --password "${{ secrets.DOCR_TOKEN }}" + helm package chart/ + helm push lunarfront-$VERSION.tgz oci://registry.digitalocean.com/lunarfront - name: Commit version bump run: | @@ -95,4 +111,4 @@ jobs: - name: Logout if: always() - run: docker logout $REGISTRY + run: docker logout registry.digitalocean.com diff --git a/Dockerfile.frontend b/Dockerfile.frontend index 35e7cbc..cb61c63 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -20,6 +20,8 @@ RUN bun run build FROM nginx:alpine COPY --from=build /app/packages/admin/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf +# nginx docker image processes templates in /etc/nginx/templates/ with envsubst at startup +COPY nginx.conf /etc/nginx/templates/default.conf.template +ENV BACKEND_URL=http://localhost:8000 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..185b86d --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: lunarfront +description: LunarFront small business management platform +type: application +version: 0.1.0 +appVersion: "0.0.0" diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..c647ec1 --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,8 @@ +{{- define "lunarfront.name" -}} +{{- .Release.Name }} +{{- end }} + +{{- define "lunarfront.labels" -}} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} diff --git a/chart/templates/backend-deployment.yaml b/chart/templates/backend-deployment.yaml new file mode 100644 index 0000000..5442533 --- /dev/null +++ b/chart/templates/backend-deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-backend + namespace: {{ .Release.Namespace }} + labels: + {{- include "lunarfront.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-backend + template: + metadata: + labels: + app: {{ .Release.Name }}-backend + spec: + imagePullSecrets: + {{- toYaml .Values.imagePullSecrets | nindent 8 }} + containers: + - name: backend + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.backend.port }} + env: + - name: PORT + value: {{ .Values.backend.port | quote }} + - name: NODE_ENV + value: production + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: lunarfront-secrets + key: database-url + - name: REDIS_URL + valueFrom: + secretKeyRef: + name: lunarfront-secrets + key: redis-url + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: lunarfront-secrets + key: jwt-secret + livenessProbe: + httpGet: + path: /v1/health + port: {{ .Values.backend.port }} + initialDelaySeconds: 15 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /v1/health + port: {{ .Values.backend.port }} + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} diff --git a/chart/templates/backend-service.yaml b/chart/templates/backend-service.yaml new file mode 100644 index 0000000..cc86b15 --- /dev/null +++ b/chart/templates/backend-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-backend + namespace: {{ .Release.Namespace }} + labels: + {{- include "lunarfront.labels" . | nindent 4 }} +spec: + selector: + app: {{ .Release.Name }}-backend + ports: + - port: {{ .Values.backend.port }} + targetPort: {{ .Values.backend.port }} diff --git a/chart/templates/frontend-deployment.yaml b/chart/templates/frontend-deployment.yaml new file mode 100644 index 0000000..f223b44 --- /dev/null +++ b/chart/templates/frontend-deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-frontend + namespace: {{ .Release.Namespace }} + labels: + {{- include "lunarfront.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-frontend + template: + metadata: + labels: + app: {{ .Release.Name }}-frontend + spec: + imagePullSecrets: + {{- toYaml .Values.imagePullSecrets | nindent 8 }} + containers: + - name: frontend + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.port }} + env: + - name: BACKEND_URL + value: "http://{{ .Release.Name }}-backend:{{ .Values.backend.port }}" + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} diff --git a/chart/templates/frontend-service.yaml b/chart/templates/frontend-service.yaml new file mode 100644 index 0000000..eb1a0de --- /dev/null +++ b/chart/templates/frontend-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-frontend + namespace: {{ .Release.Namespace }} + labels: + {{- include "lunarfront.labels" . | nindent 4 }} +spec: + selector: + app: {{ .Release.Name }}-frontend + ports: + - port: 80 + targetPort: {{ .Values.frontend.port }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 0000000..7515fab --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,29 @@ +{{- if .Values.ingress.host }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lunarfront.labels" . | nindent 4 }} + annotations: + kubernetes.io/ingress.class: {{ .Values.ingress.className }} + cert-manager.io/cluster-issuer: {{ .Values.ingress.tlsIssuer }} +spec: + ingressClassName: {{ .Values.ingress.className }} + tls: + - hosts: + - {{ .Values.ingress.host }} + secretName: {{ .Release.Name }}-tls + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-frontend + port: + number: 80 +{{- end }} diff --git a/chart/templates/valkey-deployment.yaml b/chart/templates/valkey-deployment.yaml new file mode 100644 index 0000000..9d78ed7 --- /dev/null +++ b/chart/templates/valkey-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-valkey + namespace: {{ .Release.Namespace }} + labels: + {{- include "lunarfront.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-valkey + template: + metadata: + labels: + app: {{ .Release.Name }}-valkey + spec: + containers: + - name: valkey + image: "{{ .Values.valkey.image.repository }}:{{ .Values.valkey.image.tag }}" + ports: + - containerPort: {{ .Values.valkey.port }} + resources: + {{- toYaml .Values.valkey.resources | nindent 12 }} diff --git a/chart/templates/valkey-service.yaml b/chart/templates/valkey-service.yaml new file mode 100644 index 0000000..1df6d75 --- /dev/null +++ b/chart/templates/valkey-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-valkey + namespace: {{ .Release.Namespace }} + labels: + {{- include "lunarfront.labels" . | nindent 4 }} +spec: + selector: + app: {{ .Release.Name }}-valkey + ports: + - port: {{ .Values.valkey.port }} + targetPort: {{ .Values.valkey.port }} diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 0000000..1d64d35 --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,52 @@ +backend: + image: + repository: registry.digitalocean.com/lunarfront/lunarfront-app + tag: latest + pullPolicy: Always + port: 8000 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 512Mi + +frontend: + image: + repository: registry.digitalocean.com/lunarfront/lunarfront-frontend + tag: latest + pullPolicy: Always + port: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +valkey: + image: + repository: valkey/valkey + tag: "8" + port: 6379 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +ingress: + host: "" + className: nginx + tlsIssuer: letsencrypt + +imagePullSecrets: + - name: registry-lunarfront + +# Secrets are expected to exist in-namespace as 'lunarfront-secrets' with keys: +# database-url, jwt-secret, redis-url +# These are created by the manager during provisioning, not by this chart. diff --git a/nginx.conf b/nginx.conf index a217ca2..5d7f20c 100644 --- a/nginx.conf +++ b/nginx.conf @@ -3,9 +3,9 @@ server { root /usr/share/nginx/html; index index.html; - # Proxy API and WebDAV to backend + # Proxy API and WebDAV to backend — BACKEND_URL injected at runtime via envsubst location /v1/ { - proxy_pass http://localhost:8000; + proxy_pass ${BACKEND_URL}; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -14,7 +14,7 @@ server { } location /webdav/ { - proxy_pass http://localhost:8000; + proxy_pass ${BACKEND_URL}; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;