Improve security for production podman
Some checks are pending
CI/CD Pipeline with Secure Ephemeral PiP / test-backend (push) Waiting to run
CI/CD Pipeline with Secure Ephemeral PiP / test-frontend (push) Blocked by required conditions
CI/CD Pipeline with Secure Ephemeral PiP / build-backend (push) Blocked by required conditions
CI/CD Pipeline with Secure Ephemeral PiP / build-frontend (push) Blocked by required conditions
Some checks are pending
CI/CD Pipeline with Secure Ephemeral PiP / test-backend (push) Waiting to run
CI/CD Pipeline with Secure Ephemeral PiP / test-frontend (push) Blocked by required conditions
CI/CD Pipeline with Secure Ephemeral PiP / build-backend (push) Blocked by required conditions
CI/CD Pipeline with Secure Ephemeral PiP / build-frontend (push) Blocked by required conditions
This commit is contained in:
parent
1ea4dc32e5
commit
411d9f3f35
3 changed files with 192 additions and 128 deletions
|
@ -1359,7 +1359,7 @@ The CI/CD pipeline uses ephemeral Podman-in-Podman containers with a secure four
|
||||||
|
|
||||||
#### 7.5 Production Deployment Architecture
|
#### 7.5 Production Deployment Architecture
|
||||||
|
|
||||||
The production deployment uses a separate pod configuration (`prod-pod.yaml`) that pulls built images from the Forgejo Container Registry and deploys the complete application stack.
|
The production deployment uses a separate pod configuration (`prod-pod.yml`) that pulls built images from the Forgejo Container Registry and deploys the complete application stack.
|
||||||
|
|
||||||
**Production Stack Components:**
|
**Production Stack Components:**
|
||||||
- **PostgreSQL**: Production database with persistent storage
|
- **PostgreSQL**: Production database with persistent storage
|
||||||
|
@ -1370,7 +1370,7 @@ The production deployment uses a separate pod configuration (`prod-pod.yaml`) th
|
||||||
**Deployment Flow:**
|
**Deployment Flow:**
|
||||||
1. **Production Runner**: Runs on Production Linode with `production` label
|
1. **Production Runner**: Runs on Production Linode with `production` label
|
||||||
2. **Image Pull**: Pulls latest images from Forgejo Container Registry
|
2. **Image Pull**: Pulls latest images from Forgejo Container Registry
|
||||||
3. **Stack Deployment**: Uses `prod-pod.yaml` to deploy complete stack
|
3. **Stack Deployment**: Uses `prod-pod.yml` to deploy complete stack
|
||||||
4. **Health Verification**: Ensures all services are healthy before completion
|
4. **Health Verification**: Ensures all services are healthy before completion
|
||||||
|
|
||||||
**Key Benefits:**
|
**Key Benefits:**
|
||||||
|
@ -1992,7 +1992,7 @@ sudo journalctl -u forgejo-runner.service -f --no-pager
|
||||||
When the workflow runs, it will:
|
When the workflow runs, it will:
|
||||||
|
|
||||||
1. Pull the latest Docker images from Forgejo Container Registry
|
1. Pull the latest Docker images from Forgejo Container Registry
|
||||||
2. Use the `prod-pod.yaml` file to deploy the application stack
|
2. Use the `prod-pod.yml` file to deploy the application stack
|
||||||
3. Create the necessary environment variables for production deployment
|
3. Create the necessary environment variables for production deployment
|
||||||
4. Verify that all services are healthy after deployment
|
4. Verify that all services are healthy after deployment
|
||||||
|
|
||||||
|
@ -2000,7 +2000,7 @@ The production runner will automatically handle the deployment process when you
|
||||||
|
|
||||||
#### 12.6 Understanding the Production Pod Setup
|
#### 12.6 Understanding the Production Pod Setup
|
||||||
|
|
||||||
The `prod-pod.yaml` file is specifically designed for production deployment and uses Kubernetes pod specifications:
|
The `prod-pod.yml` file is specifically designed for production deployment and uses Kubernetes pod specifications:
|
||||||
|
|
||||||
**Key Features**:
|
**Key Features**:
|
||||||
- **Image-based deployment**: Uses pre-built images from Forgejo Container Registry instead of building from source
|
- **Image-based deployment**: Uses pre-built images from Forgejo Container Registry instead of building from source
|
||||||
|
@ -2019,7 +2019,7 @@ The `prod-pod.yaml` file is specifically designed for production deployment and
|
||||||
**Deployment Process**:
|
**Deployment Process**:
|
||||||
1. The production runner pulls the latest images from Forgejo Container Registry
|
1. The production runner pulls the latest images from Forgejo Container Registry
|
||||||
2. Creates environment variables for the deployment
|
2. Creates environment variables for the deployment
|
||||||
3. Runs `podman play kube prod-pod.yaml`
|
3. Runs `podman play kube prod-pod.yml`
|
||||||
4. Waits for all services to be healthy
|
4. Waits for all services to be healthy
|
||||||
5. Verifies the deployment was successful
|
5. Verifies the deployment was successful
|
||||||
|
|
||||||
|
@ -2424,12 +2424,12 @@ After successful setup, you can clean up the installation files to remove sensit
|
||||||
|---------|--------------------------|--------------------------|
|
|---------|--------------------------|--------------------------|
|
||||||
| Test | All integration/unit tests| `ci-pod.yaml` |
|
| Test | All integration/unit tests| `ci-pod.yaml` |
|
||||||
| Build | Build & push images | Podman build & push |
|
| Build | Build & push images | Podman build & push |
|
||||||
| Deploy | Deploy to production | `prod-pod.yaml` |
|
| Deploy | Deploy to production | `prod-pod.yml` |
|
||||||
|
|
||||||
**How it works:**
|
**How it works:**
|
||||||
- **Test:** The workflow spins up a full test environment using `ci-pod.yaml` (Postgres, backend, frontend, etc.) and runs all tests inside containers.
|
- **Test:** The workflow spins up a full test environment using `ci-pod.yaml` (Postgres, backend, frontend, etc.) and runs all tests inside containers.
|
||||||
- **Build:** If tests pass, the workflow uses Podman to build backend and frontend images and push them to Forgejo Container Registry.
|
- **Build:** If tests pass, the workflow uses Podman to build backend and frontend images and push them to Forgejo Container Registry.
|
||||||
- **Deploy:** The production runner pulls images from Forgejo Container Registry and deploys the stack using `prod-pod.yaml`.
|
- **Deploy:** The production runner pulls images from Forgejo Container Registry and deploys the stack using `prod-pod.yml`.
|
||||||
|
|
||||||
**Expected Output:**
|
**Expected Output:**
|
||||||
- Each stage runs in its own isolated environment.
|
- Each stage runs in its own isolated environment.
|
||||||
|
|
185
deploy/prod-pod.yml
Normal file
185
deploy/prod-pod.yml
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: prod-pod
|
||||||
|
labels:
|
||||||
|
app: sharenet-production
|
||||||
|
annotations:
|
||||||
|
io.containers.no-new-privileges: "true"
|
||||||
|
spec:
|
||||||
|
hostname: prod-pod
|
||||||
|
# Security: run as non-root user with specific UID/GID
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: localhost/postgres:deployed
|
||||||
|
# Security: drop all capabilities, read-only root filesystem except data volume
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: postgres-secrets
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: pgdata
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
readOnly: false
|
||||||
|
# Health checks
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["pg_isready", "-U", "sharenet"]
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["pg_isready", "-U", "sharenet"]
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
# Resource limits
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
limits:
|
||||||
|
memory: "1Gi"
|
||||||
|
cpu: "500m"
|
||||||
|
|
||||||
|
- name: backend
|
||||||
|
image: localhost/backend:deployed
|
||||||
|
# Security: drop all capabilities, read-only root filesystem
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: backend-secrets
|
||||||
|
ports:
|
||||||
|
- containerPort: 3001
|
||||||
|
protocol: TCP
|
||||||
|
# Health checks
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 3001
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 3001
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
# Resource limits
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
|
||||||
|
- name: frontend
|
||||||
|
image: localhost/frontend:deployed
|
||||||
|
# Security: drop all capabilities, read-only root filesystem
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: frontend-secrets
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
protocol: TCP
|
||||||
|
# Resource limits
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
|
||||||
|
- name: nginx
|
||||||
|
image: localhost/nginx:deployed
|
||||||
|
# Security: drop all capabilities, read-only root filesystem
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
hostPort: 80
|
||||||
|
- containerPort: 443
|
||||||
|
protocol: TCP
|
||||||
|
hostPort: 443
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-conf
|
||||||
|
mountPath: /etc/nginx/nginx.conf
|
||||||
|
subPath: nginx.conf
|
||||||
|
readOnly: true
|
||||||
|
- name: nginx-cache
|
||||||
|
mountPath: /var/cache/nginx
|
||||||
|
readOnly: false
|
||||||
|
- name: letsencrypt
|
||||||
|
mountPath: /etc/letsencrypt
|
||||||
|
readOnly: true
|
||||||
|
# Health check
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 80
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
# Resource limits
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: pgdata
|
||||||
|
hostPath:
|
||||||
|
path: /opt/sharenet/volumes/pgdata
|
||||||
|
type: Directory
|
||||||
|
- name: nginx-conf
|
||||||
|
hostPath:
|
||||||
|
path: /opt/sharenet/nginx
|
||||||
|
type: Directory
|
||||||
|
- name: nginx-cache
|
||||||
|
hostPath:
|
||||||
|
path: /opt/sharenet/volumes/nginx-cache
|
||||||
|
type: Directory
|
||||||
|
- name: letsencrypt
|
||||||
|
hostPath:
|
||||||
|
path: /etc/letsencrypt
|
||||||
|
type: Directory
|
121
prod-pod.yaml
121
prod-pod.yaml
|
@ -1,121 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: sharenet-production-pod
|
|
||||||
labels:
|
|
||||||
app: sharenet-production
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: postgres
|
|
||||||
image: postgres:15-alpine
|
|
||||||
env:
|
|
||||||
- name: POSTGRES_DB
|
|
||||||
value: "sharenet"
|
|
||||||
- name: POSTGRES_USER
|
|
||||||
value: "sharenet"
|
|
||||||
- name: POSTGRES_PASSWORD
|
|
||||||
value: "${POSTGRES_PASSWORD}"
|
|
||||||
ports:
|
|
||||||
- containerPort: 5432
|
|
||||||
protocol: TCP
|
|
||||||
volumeMounts:
|
|
||||||
- name: postgres-data
|
|
||||||
mountPath: /var/lib/postgresql/data
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- pg_isready
|
|
||||||
- -U
|
|
||||||
- sharenet
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 30
|
|
||||||
timeoutSeconds: 10
|
|
||||||
failureThreshold: 3
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- pg_isready
|
|
||||||
- -U
|
|
||||||
- sharenet
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
|
|
||||||
- name: backend
|
|
||||||
image: ${REGISTRY_HOST}/${OWNER_REPO}/backend:${IMAGE_TAG}
|
|
||||||
env:
|
|
||||||
- name: DATABASE_URL
|
|
||||||
value: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}"
|
|
||||||
- name: RUST_LOG
|
|
||||||
value: "info"
|
|
||||||
- name: RUST_BACKTRACE
|
|
||||||
value: "1"
|
|
||||||
ports:
|
|
||||||
- containerPort: 3001
|
|
||||||
protocol: TCP
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 3001
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 30
|
|
||||||
timeoutSeconds: 10
|
|
||||||
failureThreshold: 3
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 3001
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
|
|
||||||
- name: frontend
|
|
||||||
image: ${REGISTRY_HOST}/${OWNER_REPO}/frontend:${IMAGE_TAG}
|
|
||||||
env:
|
|
||||||
- name: NEXT_PUBLIC_API_HOST
|
|
||||||
value: "localhost"
|
|
||||||
- name: NEXT_PUBLIC_API_PORT
|
|
||||||
value: "3001"
|
|
||||||
- name: NODE_ENV
|
|
||||||
value: "production"
|
|
||||||
ports:
|
|
||||||
- containerPort: 3000
|
|
||||||
protocol: TCP
|
|
||||||
dependsOn:
|
|
||||||
- name: backend
|
|
||||||
condition: Ready
|
|
||||||
|
|
||||||
- name: nginx
|
|
||||||
image: nginx:alpine
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
protocol: TCP
|
|
||||||
- containerPort: 443
|
|
||||||
protocol: TCP
|
|
||||||
volumeMounts:
|
|
||||||
- name: nginx-config
|
|
||||||
mountPath: /etc/nginx/nginx.conf
|
|
||||||
subPath: nginx.conf
|
|
||||||
readOnly: true
|
|
||||||
- name: nginx-ssl
|
|
||||||
mountPath: /etc/nginx/ssl
|
|
||||||
readOnly: true
|
|
||||||
dependsOn:
|
|
||||||
- name: frontend
|
|
||||||
condition: Ready
|
|
||||||
- name: backend
|
|
||||||
condition: Ready
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: postgres-data
|
|
||||||
hostPath:
|
|
||||||
path: /var/lib/postgresql/data
|
|
||||||
type: Directory
|
|
||||||
- name: nginx-config
|
|
||||||
hostPath:
|
|
||||||
path: /opt/sharenet/nginx/nginx.conf
|
|
||||||
type: File
|
|
||||||
- name: nginx-ssl
|
|
||||||
hostPath:
|
|
||||||
path: /opt/sharenet/nginx/ssl
|
|
||||||
type: Directory
|
|
Loading…
Add table
Reference in a new issue