Add automatic Kubernetes deployment
Build and deploy / build (push) Failing after 6s
Build and deploy / deploy (push) Skipped

This commit is contained in:
nicholas committed 2026-09-08 13:02:24 -05:00
1 parent ebb9789853
commit 3500561af7
10 files changed
+274

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
.git
.gitea
.gradle
.idea
android
docs
**/build
**/data
**/dist
**/node_modules
*.db
+83
View File
@@ -0,0 +1,83 @@
name: Build and deploy
on:
push:
branches:
- master
workflow_dispatch:
jobs:
build:
runs-on: shared-ci
env:
IMAGE_REGISTRY: git.uuard.com
IMAGE_NAME: nicholas/box-manifest
IMAGE_TAG: ${{ github.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test server
run: docker run --rm -v "$PWD/server:/src" -w /src golang:1.25-alpine go test ./...
- name: Log in to Gitea registry
uses: docker/login-action@v3
with:
registry: ${{ env.IMAGE_REGISTRY }}
username: nicholas
password: ${{ secrets.CD_CD_BUILD_PUSH }}
- name: Build image
run: docker build --tag "$IMAGE_REGISTRY/$IMAGE_NAME:$IMAGE_TAG" .
- name: Push image
run: docker push "$IMAGE_REGISTRY/$IMAGE_NAME:$IMAGE_TAG"
- name: Prune images
if: always()
run: docker image prune --force
deploy:
needs: build
runs-on: box-manifest-deploy
env:
IMAGE_TAG: ${{ github.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install kubectl
env:
KUBECTL_VERSION: v1.36.2
run: |
set -euo pipefail
curl -fsSLO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
curl -fsSLO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256"
printf '%s %s\n' "$(cat kubectl.sha256)" kubectl | sha256sum -c
mkdir -p "$HOME/.local/bin"
install -m 0755 kubectl "$HOME/.local/bin/kubectl"
printf '%s\n' "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Configure Kubernetes access
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
run: |
set -euo pipefail
mkdir -p ~/.kube
printf '%s' "$KUBE_CONFIG" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
kubectl auth can-i update deployments --namespace nicholas
- name: Deploy Box Manifest
run: |
set -euo pipefail
sed -E -i "s#newTag: [0-9a-z]+#newTag: ${IMAGE_TAG}#" k8s/kustomization.yaml
grep -q "newTag: ${IMAGE_TAG}$" k8s/kustomization.yaml
kubectl apply --kustomize k8s
if ! kubectl rollout status deployment/box-manifest \
--namespace nicholas --timeout=180s; then
kubectl rollout undo deployment/box-manifest --namespace nicholas
kubectl rollout status deployment/box-manifest \
--namespace nicholas --timeout=180s
exit 1
fi
+27
View File
@@ -0,0 +1,27 @@
FROM node:24-alpine AS web-build
WORKDIR /src/web
COPY web/package.json web/package-lock.json ./
RUN npm ci
COPY web/ ./
RUN npm run build
FROM golang:1.25-alpine AS server-build
WORKDIR /src/server
COPY server/go.mod server/go.sum ./
RUN go mod download
COPY server/ ./
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/box-manifest-server ./cmd/box-manifest-server
FROM alpine:3.22
RUN addgroup -g 10001 box-manifest \
&& adduser -D -H -u 10001 -G box-manifest box-manifest \
&& apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=server-build /out/box-manifest-server ./box-manifest-server
COPY --from=web-build /src/web/dist ./web
RUN mkdir /data && chown box-manifest:box-manifest /data
USER 10001:10001
EXPOSE 8080
VOLUME ["/data"]
ENTRYPOINT ["/app/box-manifest-server"]
CMD ["-address", ":8080", "-database", "/data/box-manifest.db", "-web", "/app/web"]
+21
View File
@@ -56,6 +56,27 @@ For a production-style local build, run `npm run build` before starting the Go
server. The server serves the generated `web/dist` directory at
`http://localhost:8080`.
## Container deployment
The root `Dockerfile` builds the web client and Go server into one image. Run it
locally with a persistent data volume:
```bash
docker build -t box-manifest .
docker run --rm -p 8080:8080 -v box-manifest-data:/data box-manifest
```
Kubernetes resources in `k8s/` deploy one server replica with a persistent
SQLite volume at `/data`. The deployment intentionally uses the `Recreate`
strategy so two server processes never write the database during an update.
Pushes to `master` build and publish an immutable image to the Gitea registry,
then deploy it to the `nicholas` namespace. The repository requires these
Gitea Actions secrets:
- `CD_CD_BUILD_PUSH`: registry token with package write access
- `KUBE_CONFIG`: base64-encoded kubeconfig for the namespace deployer account
Available options:
```bash
+71
View File
@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: box-manifest
labels:
app.kubernetes.io/name: box-manifest
spec:
replicas: 1
revisionHistoryLimit: 5
progressDeadlineSeconds: 180
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: box-manifest
template:
metadata:
labels:
app.kubernetes.io/name: box-manifest
spec:
imagePullSecrets:
- name: gitea-registry
securityContext:
fsGroup: 10001
fsGroupChangePolicy: OnRootMismatch
containers:
- name: box-manifest
image: git.uuard.com/nicholas/box-manifest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 2
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 6
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 250m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: box-manifest-data
+19
View File
@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: box-manifest
labels:
app.kubernetes.io/name: box-manifest
spec:
ingressClassName: traefik
rules:
- host: box-manifest.uuard.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: box-manifest
port:
name: http
+11
View File
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: nicholas
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
- pvc.yaml
images:
- name: git.uuard.com/nicholas/box-manifest
newTag: initial
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: box-manifest-data
labels:
app.kubernetes.io/name: box-manifest
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: box-manifest
labels:
app.kubernetes.io/name: box-manifest
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: box-manifest
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
+4
View File
@@ -42,6 +42,10 @@ func main() {
mux := http.NewServeMux()
apiHandler := httpapi.New(store)
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte("ok\n"))
})
mux.Handle("/api/", apiHandler)
mux.Handle("/n/", apiHandler)
mux.Handle("/", newWebHandler(*webPath))