kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(deployment): add Helm chart and improve container security (#116)

* feat(deployment): add Helm chart and improve container security

- Add Kubernetes Helm chart for deploying Kaneo
- Update README.md with Kubernetes deployment instructions
- Include documentation for various deployment scenarios
- Add .dockerignore files for api and web apps
- Implement non-root user for containers
- Optimize layer caching in Dockerfiles
- Add security headers in nginx config
- Enable gzip compression
- Improve static asset caching

Closes #80

* docs(kubernetes): improve Minikube deployment instructions

- Add local deployment guide with Minikube in main README
- Update ingress path patterns to use better regex capture groups
- Fix rewrite target annotation from /$2 to /$1
- Add detailed notes about ingress configuration
- Include OS-specific setup instructions for macOS, Linux, and Windows

Part of #80

---------

Co-authored-by: Andrej <44305048+aacevski@users.noreply.github.com>

authored by

Goran Nushkov
Andrej
and committed by
GitHub
139fb51c 731687ec

+1218 -19
+117 -1
README.md
··· 76 76 | `JWT_ACCESS` | Secret key for generating JWT tokens | 77 77 | `DB_PATH` | The path to the database file | 78 78 79 + ## 🚢 Kubernetes Deployment 80 + 81 + Kaneo can also be deployed on Kubernetes using our Helm chart: 82 + 83 + 1. Clone this repository: 84 + 85 + ```bash 86 + git clone https://github.com/usekaneo/kaneo.git 87 + cd kaneo 88 + ``` 89 + 90 + 2. Install the Helm chart: 91 + 92 + ```bash 93 + helm install kaneo ./charts/kaneo --namespace kaneo --create-namespace 94 + ``` 95 + 96 + 3. Access Kaneo: 97 + 98 + ```bash 99 + # Port forward to access both services 100 + kubectl port-forward svc/kaneo-web 5173:80 -n kaneo & 101 + kubectl port-forward svc/kaneo-api 1337:1337 -n kaneo & 102 + 103 + # Access the application at http://localhost:5173 104 + # The web frontend will communicate with the API at http://localhost:1337 105 + ``` 106 + 107 + ### Production Deployments 108 + 109 + For production environments, we recommend using Ingress to expose Kaneo: 110 + 111 + ```bash 112 + # Basic installation with ingress 113 + helm install kaneo ./charts/kaneo \ 114 + --namespace kaneo \ 115 + --create-namespace \ 116 + --set ingress.enabled=true \ 117 + --set ingress.className=nginx \ 118 + --set "ingress.hosts[0].host=kaneo.example.com" 119 + ``` 120 + 121 + For detailed production deployment examples, including: 122 + 123 + - TLS configuration 124 + - Cert-manager integration 125 + - Path rewriting with regex capture groups 126 + - Gateway API usage 127 + - Resource configuration 128 + 129 + Please refer to the [Helm chart documentation](./charts/kaneo/README.md). 130 + 131 + ### Local Deployments with Minikube 132 + 133 + For local deployments with Minikube: 134 + 135 + 1. Start Minikube: 136 + 137 + ```bash 138 + minikube start 139 + ``` 140 + 141 + 2. Install the Helm chart with Ingress enabled: 142 + 143 + ```bash 144 + helm install kaneo ./charts/kaneo \ 145 + --namespace kaneo \ 146 + --create-namespace \ 147 + --set ingress.enabled=true \ 148 + --set ingress.className=nginx \ 149 + --set "ingress.hosts[0].host=kaneo.local" 150 + ``` 151 + 152 + 3. Enable the Ingress addon if not already enabled: 153 + 154 + ```bash 155 + minikube addons enable ingress 156 + ``` 157 + 158 + 4. Access Kaneo based on your OS: 159 + 160 + #### macOS 161 + 162 + For macOS, you need to use `minikube tunnel` to access the Ingress: 163 + 164 + ```bash 165 + # Start minikube tunnel in a separate terminal 166 + minikube tunnel 167 + ``` 168 + 169 + Update your /etc/hosts file: 170 + 171 + ```bash 172 + # Add to /etc/hosts 173 + 127.0.0.1 kaneo.local 174 + ``` 175 + 176 + Access Kaneo at http://kaneo.local 177 + 178 + #### Linux/Windows 179 + 180 + Get the Minikube IP: 181 + 182 + ```bash 183 + minikube ip 184 + ``` 185 + 186 + Update your hosts file with the Minikube IP: 187 + 188 + ```bash 189 + # Add to /etc/hosts (Linux) or C:\Windows\System32\drivers\etc\hosts (Windows) 190 + 192.168.49.2 kaneo.local # Replace with the actual Minikube IP 191 + ``` 192 + 193 + Access Kaneo at http://kaneo.local 194 + 79 195 ## 📖 Documentation 80 196 81 197 For detailed instructions and documentation, visit our [Documentation](https://kaneo.app/quick-start). ··· 106 222 107 223 ## 📝 License 108 224 109 - This project is licensed under the [MIT License](LICENSE). 225 + This project is licensed under the [MIT License](LICENSE).
+38
apps/api/.dockerignore
··· 1 + # Version control 2 + .git 3 + .gitignore 4 + 5 + # Build artifacts 6 + node_modules 7 + dist 8 + build 9 + *.log 10 + 11 + # Development files 12 + .env 13 + .env.* 14 + !.env.production 15 + *.local 16 + 17 + # Editor directories and files 18 + .vscode 19 + .idea 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw? 25 + 26 + # Testing 27 + coverage 28 + *.test.ts 29 + *.spec.ts 30 + test 31 + 32 + # Documentation 33 + docs 34 + *.md 35 + 36 + # Temporary files 37 + .DS_Store 38 + Thumbs.db
+26 -9
apps/api/Dockerfile
··· 8 8 g++ \ 9 9 && rm -rf /var/lib/apt/lists/* 10 10 11 - COPY package.json . 12 - COPY bun.lockb . 13 - COPY apps/api/package.json ./apps/api/package.json 14 - COPY packages/typescript-config/package.json ./packages/typescript-config/package.json 11 + # Copy package files first for better layer caching 12 + COPY package.json bun.lockb ./ 13 + COPY apps/api/package.json ./apps/api/ 14 + COPY packages/typescript-config/package.json ./packages/typescript-config/ 15 15 16 + # Install dependencies 16 17 RUN bun install --no-frozen-lockfile 17 18 19 + # Copy source code 18 20 COPY packages/typescript-config ./packages/typescript-config 19 21 COPY apps/api ./apps/api 20 22 23 + # Production stage 21 24 FROM oven/bun:1-alpine AS runtime 25 + 26 + # Create non-root user 27 + RUN addgroup -g 1001 appuser && \ 28 + adduser -u 1001 -G appuser -D appuser 29 + 22 30 WORKDIR /app/apps/api 23 31 24 - RUN mkdir -p /app/apps/api/data && chmod 777 /app/apps/api/data 32 + # Create data directory with proper permissions 33 + RUN mkdir -p /app/apps/api/data && \ 34 + chown -R appuser:appuser /app 25 35 26 - COPY --from=base /app/apps/api ./ 27 - COPY --from=base /app/node_modules ../node_modules 28 - COPY --from=base /app/packages ../packages 36 + # Copy built files from base stage 37 + COPY --from=base --chown=appuser:appuser /app/apps/api ./ 38 + COPY --from=base --chown=appuser:appuser /app/node_modules ../node_modules 39 + COPY --from=base --chown=appuser:appuser /app/packages ../packages 29 40 41 + # Set environment variables 30 42 ENV NODE_ENV=production 43 + ENV BUN_ENV=production 44 + 45 + # Switch to non-root user 46 + USER appuser 31 47 32 48 EXPOSE 1337 33 49 34 - CMD ["bun", "run", "/app/apps/api/src/index.ts"] 50 + # Use exec form of CMD for proper signal handling 51 + CMD ["bun", "run", "/app/apps/api/src/index.ts"]
+44
apps/web/.dockerignore
··· 1 + # Version control 2 + .git 3 + .gitignore 4 + 5 + # Build artifacts 6 + node_modules 7 + dist 8 + build 9 + *.log 10 + 11 + # Development files 12 + .env 13 + .env.* 14 + !.env.production 15 + *.local 16 + 17 + # Editor directories and files 18 + .vscode 19 + .idea 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw? 25 + 26 + # Testing 27 + coverage 28 + *.test.ts 29 + *.spec.ts 30 + test 31 + __tests__ 32 + 33 + # Documentation 34 + docs 35 + *.md 36 + 37 + # Temporary files 38 + .DS_Store 39 + Thumbs.db 40 + 41 + # Debug files 42 + npm-debug.log* 43 + yarn-debug.log* 44 + yarn-error.log*
+43 -7
apps/web/Dockerfile
··· 9 9 10 10 WORKDIR /app 11 11 12 + # Copy package files first for better layer caching 12 13 COPY package.json bun.lockb ./ 14 + COPY apps/web/package.json ./apps/web/ 15 + COPY packages/typescript-config/package.json ./packages/typescript-config/ 16 + COPY packages/libs/package.json ./packages/libs/ 17 + COPY apps/api/package.json ./apps/api/ 13 18 14 - COPY packages ./packages 19 + # Install dependencies 20 + RUN bun install --no-frozen-lockfile 21 + 22 + # Copy only necessary source code 23 + COPY packages/typescript-config ./packages/typescript-config 24 + COPY packages/libs ./packages/libs 15 25 COPY apps/api ./apps/api 16 26 COPY apps/web ./apps/web 17 27 18 - RUN bun install --no-frozen-lockfile 19 - 28 + # Build the application 20 29 WORKDIR /app/apps/web 21 30 RUN bun run build 22 31 32 + # Production stage with pinned version 23 33 FROM nginx:alpine 24 34 25 - COPY --from=builder /app/apps/web/dist /usr/share/nginx/html 35 + # Create non-root user for nginx 36 + RUN addgroup -g 1001 appuser && \ 37 + adduser -u 1001 -G appuser -D appuser && \ 38 + # Set permissions for nginx directories 39 + chown -R appuser:appuser /var/cache/nginx && \ 40 + chmod -R 755 /var/cache/nginx && \ 41 + # Create directory for pid file 42 + mkdir -p /var/run/nginx && \ 43 + chown -R appuser:appuser /var/run/nginx && \ 44 + chmod -R 755 /var/run/nginx && \ 45 + # Set permissions for nginx configuration 46 + touch /var/run/nginx.pid && \ 47 + chown appuser:appuser /var/run/nginx.pid && \ 48 + chmod 644 /var/run/nginx.pid && \ 49 + # Update nginx configuration to run as non-root 50 + sed -i 's/user nginx;/user appuser;/' /etc/nginx/nginx.conf && \ 51 + # Remove the user directive completely to avoid warnings 52 + sed -i 's/user appuser;//' /etc/nginx/nginx.conf 53 + 54 + # Copy built files from builder stage 55 + COPY --from=builder --chown=appuser:appuser /app/apps/web/dist /usr/share/nginx/html 26 56 27 - COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf 57 + # Copy nginx configuration 58 + COPY --chown=appuser:appuser apps/web/nginx.conf /etc/nginx/conf.d/default.conf 28 59 29 - COPY apps/web/env.sh /docker-entrypoint.d/env.sh 60 + # Copy and set permissions for environment script 61 + COPY --chown=appuser:appuser apps/web/env.sh /docker-entrypoint.d/env.sh 30 62 RUN chmod +x /docker-entrypoint.d/env.sh 31 63 64 + # Switch to non-root user 65 + USER appuser 66 + 32 67 EXPOSE 80 33 68 34 - CMD ["nginx", "-g", "daemon off;"] 69 + # Use exec form of CMD for proper signal handling 70 + CMD ["nginx", "-g", "daemon off;"]
+8 -1
apps/web/env.sh
··· 1 1 #!/bin/sh 2 + set -e 3 + 4 + echo "Replacing environment variables in static files..." 5 + 6 + # Process only KANEO_ prefixed environment variables 2 7 for i in $(env | grep KANEO_) 3 8 do 4 9 key=$(echo $i | cut -d '=' -f 1) ··· 6 11 echo $key=$value 7 12 8 13 find /usr/share/nginx/html -type f \( -name '*.js' -o -name '*.css' \) -exec sed -i "s|${key}|${value}|g" '{}' + 9 - done 14 + done 15 + 16 + echo "Environment variable replacement complete"
+23 -1
apps/web/nginx.conf
··· 2 2 listen 80; 3 3 server_name localhost; 4 4 5 + # Security headers 6 + add_header X-Content-Type-Options "nosniff" always; 7 + add_header X-Frame-Options "SAMEORIGIN" always; 8 + add_header X-XSS-Protection "1; mode=block" always; 9 + add_header Referrer-Policy "strict-origin-when-cross-origin" always; 10 + 11 + # Enable gzip compression 12 + gzip on; 13 + gzip_vary on; 14 + gzip_min_length 1000; 15 + gzip_proxied any; 16 + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 17 + gzip_comp_level 6; 18 + 19 + # Root location 5 20 location / { 6 21 root /usr/share/nginx/html; 7 22 index index.html; 8 23 try_files $uri $uri/ /index.html; 9 24 } 10 25 11 - } 26 + # Cache static assets 27 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { 28 + root /usr/share/nginx/html; 29 + expires 30d; 30 + add_header Cache-Control "public, max-age=2592000"; 31 + access_log off; 32 + } 33 + }
+30
charts/kaneo/Chart.yaml
··· 1 + apiVersion: v2 2 + name: kaneo 3 + description: A Helm chart for Kaneo - an open source project management platform focused on simplicity and efficiency 4 + type: application 5 + version: 0.1.0 6 + appVersion: "1.0.0" 7 + keywords: 8 + - project-management 9 + - kanban 10 + - task-management 11 + - productivity 12 + - collaboration 13 + home: https://kaneo.app 14 + sources: 15 + - https://github.com/usekaneo/kaneo 16 + icon: https://assets.kaneo.app/logo-mono-rounded.png 17 + maintainers: 18 + - name: Kaneo Team 19 + url: https://github.com/usekaneo 20 + email: info@kaneo.app 21 + annotations: 22 + artifacthub.io/license: MIT 23 + artifacthub.io/containsSecurityUpdates: "false" 24 + artifacthub.io/prerelease: "false" 25 + artifacthub.io/features: | 26 + - Simple & Fast: Minimalist interface with powerful features 27 + - Self-hosted: Full control over your data 28 + - Customizable: Make it yours with extensive customization options 29 + - Open Source: MIT licensed, free forever 30 + kubeVersion: ">=1.19.0-0"
+326
charts/kaneo/README.md
··· 1 + # Kaneo Helm Chart 2 + 3 + This Helm chart deploys [Kaneo](https://kaneo.app) - an open source project management platform focused on simplicity and efficiency. 4 + 5 + ## Introduction 6 + 7 + This chart bootstraps a Kaneo deployment on a Kubernetes cluster using the Helm package manager. It deploys both the API backend and Web frontend components, with optional ingress resources. 8 + 9 + ## Prerequisites 10 + 11 + - Kubernetes 1.19+ 12 + - Helm 3.2.0+ 13 + - PV provisioner support in the underlying infrastructure (if persistence is enabled) 14 + 15 + ## Installing the Chart 16 + 17 + To install the chart with the release name `my-kaneo`: 18 + 19 + ```bash 20 + helm install my-kaneo ./charts/kaneo 21 + ``` 22 + 23 + The command deploys Kaneo on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. 24 + 25 + ## Uninstalling the Chart 26 + 27 + To uninstall/delete the `my-kaneo` deployment: 28 + 29 + ```bash 30 + helm uninstall my-kaneo 31 + ``` 32 + 33 + ## Parameters 34 + 35 + ### Global parameters 36 + 37 + | Name | Description | Value | 38 + | ------------------------ | ------------------------------------------------------------------------------------------------------------------ | ----------- | 39 + | `nameOverride` | String to partially override the fullname template (will maintain the release name) | `""` | 40 + | `fullnameOverride` | String to fully override the fullname template | `""` | 41 + | `replicaCount` | Number of replicas (ignored if autoscaling is enabled) | `1` | 42 + 43 + ### Autoscaling parameters 44 + 45 + | Name | Description | Value | 46 + | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------- | 47 + | `autoscaling.enabled` | Enable autoscaling for the deployment | `false` | 48 + | `autoscaling.minReplicas` | Minimum number of replicas | `1` | 49 + | `autoscaling.maxReplicas` | Maximum number of replicas | `10` | 50 + | `autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization percentage | `80` | 51 + 52 + ### API Backend parameters 53 + 54 + | Name | Description | Value | 55 + | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------- | 56 + | `api.enabled` | Enable API deployment | `true` | 57 + | `api.replicaCount` | Number of API replicas | `1` | 58 + | `api.image.repository` | API image repository | `ghcr.io/usekaneo/api` | 59 + | `api.image.tag` | API image tag | `latest` | 60 + | `api.image.pullPolicy` | API image pull policy | `IfNotPresent` | 61 + | `api.service.type` | API service type | `ClusterIP` | 62 + | `api.service.port` | API service port | `1337` | 63 + | `api.service.targetPort` | API container port | `1337` | 64 + | `api.env` | Environment variables for the API container | See `values.yaml` | 65 + | `api.env.jwtAccess` | Secret key for JWT token generation (ignored if existingSecret is enabled) | `change_me` | 66 + | `api.env.existingSecret.enabled` | Whether to use an existing secret for JWT access token | `false` | 67 + | `api.env.existingSecret.name` | Name of the existing secret containing the JWT access token | `""` | 68 + | `api.env.existingSecret.key` | Key in the existing secret that contains the JWT access token | `jwt-access` | 69 + | `api.persistence.enabled` | Enable persistence for SQLite database | `true` | 70 + | `api.persistence.mountPath` | Path where the SQLite database will be stored | `/app/apps/api/data` | 71 + | `api.persistence.dbFilename` | Name of the SQLite database file | `kaneo.db` | 72 + | `api.persistence.accessMode` | PVC access mode | `ReadWriteOnce` | 73 + | `api.persistence.size` | PVC size | `1Gi` | 74 + | `api.persistence.storageClass` | PVC storage class | `""` | 75 + | `api.resources` | Resource requests and limits for the API container (optional, disabled by default) | `{}` | 76 + | `api.autoscaling.enabled` | Enable autoscaling for the API deployment | `false` | 77 + | `api.autoscaling.minReplicas` | Minimum number of replicas | `1` | 78 + | `api.autoscaling.maxReplicas` | Maximum number of replicas | `10` | 79 + | `api.autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization percentage | `80` | 80 + 81 + ### Web Frontend parameters 82 + 83 + | Name | Description | Value | 84 + | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------- | 85 + | `web.enabled` | Enable Web deployment | `true` | 86 + | `web.replicaCount` | Number of Web replicas | `1` | 87 + | `web.image.repository` | Web image repository | `ghcr.io/usekaneo/web` | 88 + | `web.image.tag` | Web image tag | `latest` | 89 + | `web.image.pullPolicy` | Web image pull policy | `IfNotPresent` | 90 + | `web.service.type` | Web service type | `ClusterIP` | 91 + | `web.service.port` | Web service port | `80` | 92 + | `web.service.targetPort` | Web container port | `80` | 93 + | `web.env` | Environment variables for the Web container | See `values.yaml` | 94 + | `web.resources` | Resource requests and limits for the Web container (optional, disabled by default) | `{}` | 95 + | `web.autoscaling.enabled` | Enable autoscaling for the Web deployment | `false` | 96 + | `web.autoscaling.minReplicas` | Minimum number of replicas | `1` | 97 + | `web.autoscaling.maxReplicas` | Maximum number of replicas | `10` | 98 + | `web.autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization percentage | `80` | 99 + 100 + ### Ingress parameters 101 + 102 + | Name | Description | Value | 103 + | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------- | 104 + | `ingress.enabled` | Enable ingress | `false` | 105 + | `ingress.className` | Ingress class name | `""` | 106 + | `ingress.annotations` | Ingress annotations | `{}` | 107 + | `ingress.hosts` | Ingress hosts configuration | See `values.yaml` | 108 + | `ingress.tls` | Ingress TLS configuration | `[]` | 109 + 110 + ## Configuration Examples 111 + 112 + ### Minimal Configuration 113 + 114 + ```yaml 115 + # values.yaml 116 + api: 117 + env: 118 + jwtAccess: "your-secure-jwt-secret" 119 + 120 + web: 121 + env: 122 + apiUrl: "https://your-domain.com" 123 + 124 + ingress: 125 + enabled: true 126 + className: nginx 127 + annotations: 128 + nginx.ingress.kubernetes.io/rewrite-target: /$1 129 + hosts: 130 + - host: your-domain.com 131 + paths: 132 + - path: /?(.*) 133 + pathType: Prefix 134 + service: web 135 + port: 80 136 + - path: /api/?(.*) 137 + pathType: Prefix 138 + service: api 139 + port: 1337 140 + ``` 141 + 142 + ### Production Configuration with TLS 143 + 144 + ```yaml 145 + # values.yaml 146 + replicaCount: 1 147 + # Enable and configure resources for production 148 + api: 149 + resources: 150 + limits: 151 + cpu: 1000m 152 + memory: 1Gi 153 + requests: 154 + cpu: 200m 155 + memory: 256Mi 156 + env: 157 + jwtAccess: "your-secure-jwt-secret" 158 + persistence: 159 + size: 10Gi 160 + storageClass: "managed-premium" 161 + 162 + web: 163 + resources: 164 + limits: 165 + cpu: 500m 166 + memory: 512Mi 167 + requests: 168 + cpu: 100m 169 + memory: 128Mi 170 + env: 171 + apiUrl: "https://your-domain.com" 172 + 173 + ingress: 174 + enabled: true 175 + className: nginx 176 + annotations: 177 + cert-manager.io/cluster-issuer: letsencrypt-prod 178 + nginx.ingress.kubernetes.io/ssl-redirect: "true" 179 + nginx.ingress.kubernetes.io/rewrite-target: /$1 180 + hosts: 181 + - host: your-domain.com 182 + paths: 183 + - path: / 184 + pathType: Prefix 185 + service: web 186 + port: 80 187 + - path: /api/?(.*) 188 + pathType: Prefix 189 + service: api 190 + port: 1337 191 + tls: 192 + - secretName: kaneo-tls 193 + hosts: 194 + - your-domain.com 195 + ``` 196 + 197 + ### Using an Existing Secret for Sensitive Data 198 + 199 + For production environments, it's recommended to store sensitive data like the JWT access token in a Kubernetes Secret. You can create a secret and configure the chart to use it: 200 + 201 + ```bash 202 + # Create a Secret for the JWT access token 203 + kubectl create secret generic kaneo-secrets \ 204 + --namespace kaneo \ 205 + --from-literal=jwt-access="your-secure-jwt-secret" 206 + ``` 207 + 208 + Then reference this secret in your values: 209 + 210 + ```yaml 211 + # values.yaml 212 + api: 213 + env: 214 + # The jwtAccess value will be ignored when existingSecret is enabled 215 + existingSecret: 216 + enabled: true 217 + name: "kaneo-secrets" 218 + key: "jwt-access" 219 + ``` 220 + 221 + ## Persistence 222 + 223 + The chart mounts a Persistent Volume for the SQLite database used by the API component. The volume is mounted at `/app/apps/api/data` in the API container. 224 + 225 + ## Single Pod Architecture 226 + 227 + This chart deploys both the API and Web containers in a single pod. This architecture provides several benefits: 228 + 229 + 1. **Simplified Connectivity**: The web frontend can connect directly to the API via localhost, eliminating cross-origin issues 230 + 2. **Co-location**: Ensures the web and API components are always deployed together 231 + 3. **Reduced Complexity**: Simplifies the deployment and configuration 232 + 233 + With this approach, when `web.env.apiUrl` is not set, the web container automatically connects to the API at `http://localhost:1337` without requiring any port forwarding or special configuration. 234 + 235 + ### Production Environment 236 + 237 + For production deployments, you should: 238 + 239 + 1. Set `web.env.apiUrl` to your domain (e.g., "https://your-domain.com") 240 + 2. Use an Ingress controller to expose the application 241 + 3. Configure TLS for secure access 242 + 4. Set appropriate resource limits and requests 243 + 244 + ```yaml 245 + ingress: 246 + enabled: true 247 + className: nginx 248 + annotations: 249 + cert-manager.io/cluster-issuer: letsencrypt-prod 250 + nginx.ingress.kubernetes.io/ssl-redirect: "true" 251 + nginx.ingress.kubernetes.io/rewrite-target: /$1 252 + hosts: 253 + - host: your-domain.com 254 + paths: 255 + - path: / 256 + pathType: Prefix 257 + service: web 258 + port: 80 259 + - path: /api/?(.*) 260 + pathType: Prefix 261 + service: api 262 + port: 1337 263 + tls: 264 + - secretName: kaneo-tls 265 + hosts: 266 + - your-domain.com 267 + ``` 268 + 269 + ## Using Gateway API 270 + 271 + As an alternative to Ingress, you can use the Kubernetes Gateway API for more advanced routing capabilities: 272 + 273 + ```yaml 274 + # kaneo-gateway.yaml 275 + apiVersion: gateway.networking.k8s.io/v1beta1 276 + kind: HTTPRoute 277 + metadata: 278 + name: kaneo 279 + namespace: kaneo 280 + spec: 281 + parentRefs: 282 + - name: main-gateway # Your gateway name 283 + namespace: gateway-system # Your gateway namespace 284 + sectionName: https 285 + hostnames: 286 + - "kaneo.example.com" 287 + rules: 288 + # Frontend route (root path) 289 + - matches: 290 + - path: 291 + type: PathPrefix 292 + value: / 293 + backendRefs: 294 + - name: kaneo-web 295 + port: 80 296 + # API route (api path prefix) 297 + - matches: 298 + - path: 299 + type: PathPrefix 300 + value: /api 301 + backendRefs: 302 + - name: kaneo-api 303 + port: 1337 304 + filters: 305 + - type: URLRewrite 306 + urlRewrite: 307 + path: 308 + type: ReplacePrefixMatch 309 + replacePrefixMatch: / 310 + ``` 311 + 312 + Apply the Gateway configuration: 313 + 314 + ```bash 315 + kubectl apply -f kaneo-gateway.yaml 316 + ``` 317 + 318 + ## Security 319 + 320 + For production deployments, consider the following security recommendations: 321 + 322 + 1. Use a secure JWT_ACCESS secret, preferably stored in a Kubernetes Secret 323 + 2. Enable TLS for ingress or Gateway API 324 + 3. Enable and set resource limits to prevent resource exhaustion 325 + 4. Use a dedicated storage class for the SQLite database 326 + 5. Consider using a network policy to restrict traffic between components
+62
charts/kaneo/templates/NOTES.txt
··· 1 + Thank you for installing {{ .Chart.Name }}. 2 + 3 + Your release is named {{ .Release.Name }}. 4 + 5 + To learn more about the release, try: 6 + 7 + $ helm status {{ .Release.Name }} 8 + $ helm get all {{ .Release.Name }} 9 + 10 + {{- if .Values.ingress.enabled }} 11 + You can access the application using these URLs: 12 + {{- range .Values.ingress.hosts }} 13 + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }} 14 + {{- end }} 15 + 16 + Note: This chart uses path patterns with regex capture groups for nginx: 17 + - /?(.*) -> for web frontend 18 + - /api/?(.*) -> for API requests 19 + Make sure your ingress controller supports the configured annotations: 20 + nginx.ingress.kubernetes.io/rewrite-target: /$1 21 + {{- else }} 22 + To access the application, you need to set up port forwarding to both services: 23 + 24 + $ kubectl port-forward svc/{{ include "kaneo.fullname" . }}-web 5173:{{ .Values.web.service.port }} -n {{ .Release.Namespace }} & 25 + $ kubectl port-forward svc/{{ include "kaneo.fullname" . }}-api 1337:{{ .Values.api.service.port }} -n {{ .Release.Namespace }} & 26 + 27 + Then access the application at http://localhost:5173 28 + The web frontend will communicate with the API at http://localhost:1337 29 + 30 + Alternatively, you can expose the services using an Ingress by setting .Values.ingress.enabled=true 31 + {{- end }} 32 + 33 + NOTES: 34 + 1. Kaneo is configured with SQLite database for persistence. 35 + {{- if .Values.api.persistence.enabled }} 36 + A PersistentVolumeClaim is used to store the SQLite database file. 37 + {{- else }} 38 + WARNING: Persistence is disabled. Your data will be lost when the pod is terminated. 39 + To enable persistence, set .Values.api.persistence.enabled=true 40 + {{- end }} 41 + 42 + 2. Important environment variables: 43 + - API: 44 + - JWT_ACCESS: Secret key for generating JWT tokens (currently set to: {{ .Values.api.env.jwtAccess }}) 45 + - DB_PATH: Path to the SQLite database file (set to: "{{ .Values.api.persistence.mountPath }}/{{ .Values.api.persistence.dbFilename }}") 46 + 47 + - Web: 48 + - KANEO_API_URL: URL of the API service (set to: "{{- if .Values.web.env.apiUrl -}} 49 + {{- if hasSuffix "/" .Values.web.env.apiUrl -}} 50 + {{- .Values.web.env.apiUrl -}}api 51 + {{- else -}} 52 + {{- .Values.web.env.apiUrl -}}/api 53 + {{- end -}} 54 + {{- else -}} 55 + http://localhost:{{ .Values.api.service.targetPort }} 56 + {{- end -}}") 57 + 58 + You can customize these values in your values.yaml file. 59 + 60 + 3. IMPORTANT: This chart uses a combined pod approach where both the API and Web containers 61 + run in the same pod. This allows the web frontend to connect directly to the API via 62 + localhost, simplifying the deployment and eliminating cross-origin issues.
+104
charts/kaneo/templates/_helpers.tpl
··· 1 + {{/* 2 + Expand the name of the chart. 3 + */}} 4 + {{- define "kaneo.name" -}} 5 + {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 + {{- end }} 7 + 8 + {{/* 9 + Create a default fully qualified app name. 10 + We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 + If release name contains chart name it will be used as a full name. 12 + */}} 13 + {{- define "kaneo.fullname" -}} 14 + {{- if .Values.fullnameOverride }} 15 + {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 + {{- else }} 17 + {{- $name := default .Chart.Name .Values.nameOverride }} 18 + {{- if contains $name .Release.Name }} 19 + {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 + {{- else }} 21 + {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 + {{- end }} 23 + {{- end }} 24 + {{- end }} 25 + 26 + {{/* 27 + Create chart name and version as used by the chart label. 28 + */}} 29 + {{- define "kaneo.chart" -}} 30 + {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 + {{- end }} 32 + 33 + {{/* 34 + Common labels 35 + */}} 36 + {{- define "kaneo.labels" -}} 37 + helm.sh/chart: {{ include "kaneo.chart" . }} 38 + {{ include "kaneo.selectorLabels" . }} 39 + {{- if .Chart.AppVersion }} 40 + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 + {{- end }} 42 + app.kubernetes.io/managed-by: {{ .Release.Service }} 43 + {{- end }} 44 + 45 + {{/* 46 + Selector labels 47 + */}} 48 + {{- define "kaneo.selectorLabels" -}} 49 + app.kubernetes.io/name: {{ include "kaneo.name" . }} 50 + app.kubernetes.io/instance: {{ .Release.Name }} 51 + {{- end }} 52 + 53 + {{/* 54 + Create the name of the service account to use 55 + */}} 56 + {{- define "kaneo.serviceAccountName" -}} 57 + {{- if .Values.serviceAccount.create }} 58 + {{- default (include "kaneo.fullname" .) .Values.serviceAccount.name }} 59 + {{- else }} 60 + {{- default "default" .Values.serviceAccount.name }} 61 + {{- end }} 62 + {{- end }} 63 + 64 + {{/* 65 + API component common labels 66 + */}} 67 + {{- define "kaneo.api.labels" -}} 68 + helm.sh/chart: {{ include "kaneo.chart" . }} 69 + {{ include "kaneo.api.selectorLabels" . }} 70 + {{- if .Chart.AppVersion }} 71 + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 72 + {{- end }} 73 + app.kubernetes.io/managed-by: {{ .Release.Service }} 74 + app.kubernetes.io/component: api 75 + {{- end }} 76 + 77 + {{/* 78 + API component selector labels 79 + */}} 80 + {{- define "kaneo.api.selectorLabels" -}} 81 + app.kubernetes.io/name: {{ include "kaneo.name" . }}-api 82 + app.kubernetes.io/instance: {{ .Release.Name }} 83 + {{- end }} 84 + 85 + {{/* 86 + Web component common labels 87 + */}} 88 + {{- define "kaneo.web.labels" -}} 89 + helm.sh/chart: {{ include "kaneo.chart" . }} 90 + {{ include "kaneo.web.selectorLabels" . }} 91 + {{- if .Chart.AppVersion }} 92 + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 93 + {{- end }} 94 + app.kubernetes.io/managed-by: {{ .Release.Service }} 95 + app.kubernetes.io/component: web 96 + {{- end }} 97 + 98 + {{/* 99 + Web component selector labels 100 + */}} 101 + {{- define "kaneo.web.selectorLabels" -}} 102 + app.kubernetes.io/name: {{ include "kaneo.name" . }}-web 103 + app.kubernetes.io/instance: {{ .Release.Name }} 104 + {{- end }}
+105
charts/kaneo/templates/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: {{ include "kaneo.fullname" . }} 5 + labels: 6 + {{- include "kaneo.labels" . | nindent 4 }} 7 + spec: 8 + {{- if not .Values.autoscaling.enabled }} 9 + replicas: {{ .Values.replicaCount }} 10 + {{- end }} 11 + selector: 12 + matchLabels: 13 + {{- include "kaneo.selectorLabels" . | nindent 6 }} 14 + template: 15 + metadata: 16 + {{- with .Values.podAnnotations }} 17 + annotations: 18 + {{- toYaml . | nindent 8 }} 19 + {{- end }} 20 + labels: 21 + {{- include "kaneo.selectorLabels" . | nindent 8 }} 22 + spec: 23 + serviceAccountName: {{ include "kaneo.serviceAccountName" . }} 24 + securityContext: 25 + {{- toYaml .Values.podSecurityContext | nindent 8 }} 26 + containers: 27 + - name: api 28 + securityContext: 29 + {{- toYaml .Values.api.securityContext | nindent 12 }} 30 + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag | default .Chart.AppVersion }}" 31 + imagePullPolicy: {{ .Values.api.image.pullPolicy }} 32 + ports: 33 + - name: api 34 + containerPort: {{ .Values.api.service.targetPort }} 35 + protocol: TCP 36 + env: 37 + - name: JWT_ACCESS 38 + {{- if .Values.api.env.existingSecret.enabled }} 39 + valueFrom: 40 + secretKeyRef: 41 + name: {{ .Values.api.env.existingSecret.name }} 42 + key: {{ .Values.api.env.existingSecret.key }} 43 + {{- else }} 44 + value: "{{ .Values.api.env.jwtAccess }}" 45 + {{- end }} 46 + - name: DB_PATH 47 + value: "{{ .Values.api.persistence.mountPath }}/{{ .Values.api.persistence.dbFilename }}" 48 + livenessProbe: 49 + {{- toYaml .Values.api.livenessProbe | nindent 12 }} 50 + readinessProbe: 51 + {{- toYaml .Values.api.readinessProbe | nindent 12 }} 52 + resources: 53 + {{- toYaml .Values.api.resources | nindent 12 }} 54 + {{- if .Values.api.persistence.enabled }} 55 + volumeMounts: 56 + - name: sqlite-data 57 + mountPath: {{ .Values.api.persistence.mountPath }} 58 + {{- end }} 59 + 60 + - name: web 61 + securityContext: 62 + {{- toYaml .Values.web.securityContext | nindent 12 }} 63 + image: "{{ .Values.web.image.repository }}:{{ .Values.web.image.tag | default .Chart.AppVersion }}" 64 + imagePullPolicy: {{ .Values.web.image.pullPolicy }} 65 + ports: 66 + - name: web 67 + containerPort: {{ .Values.web.service.targetPort }} 68 + protocol: TCP 69 + env: 70 + - name: KANEO_API_URL 71 + value: "{{- if .Values.web.env.apiUrl -}} 72 + {{- if hasSuffix "/" .Values.web.env.apiUrl -}} 73 + {{- .Values.web.env.apiUrl -}}api 74 + {{- else -}} 75 + {{- .Values.web.env.apiUrl -}}/api 76 + {{- end -}} 77 + {{- else -}} 78 + http://localhost:{{ .Values.api.service.targetPort }} 79 + {{- end -}}" 80 + livenessProbe: 81 + {{- toYaml .Values.web.livenessProbe | nindent 12 }} 82 + readinessProbe: 83 + {{- toYaml .Values.web.readinessProbe | nindent 12 }} 84 + resources: 85 + {{- toYaml .Values.web.resources | nindent 12 }} 86 + 87 + {{- if .Values.api.persistence.enabled }} 88 + volumes: 89 + - name: sqlite-data 90 + persistentVolumeClaim: 91 + claimName: {{ include "kaneo.fullname" . }}-sqlite-data 92 + {{- end }} 93 + 94 + {{- with .Values.nodeSelector }} 95 + nodeSelector: 96 + {{- toYaml . | nindent 8 }} 97 + {{- end }} 98 + {{- with .Values.affinity }} 99 + affinity: 100 + {{- toYaml . | nindent 8 }} 101 + {{- end }} 102 + {{- with .Values.tolerations }} 103 + tolerations: 104 + {{- toYaml . | nindent 8 }} 105 + {{- end }}
+32
charts/kaneo/templates/hpa.yaml
··· 1 + {{- if .Values.autoscaling.enabled }} 2 + apiVersion: autoscaling/v2 3 + kind: HorizontalPodAutoscaler 4 + metadata: 5 + name: {{ include "kaneo.fullname" . }} 6 + labels: 7 + {{- include "kaneo.labels" . | nindent 4 }} 8 + spec: 9 + scaleTargetRef: 10 + apiVersion: apps/v1 11 + kind: Deployment 12 + name: {{ include "kaneo.fullname" . }} 13 + minReplicas: {{ .Values.autoscaling.minReplicas }} 14 + maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 + metrics: 16 + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 + - type: Resource 18 + resource: 19 + name: cpu 20 + target: 21 + type: Utilization 22 + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 23 + {{- end }} 24 + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 25 + - type: Resource 26 + resource: 27 + name: memory 28 + target: 29 + type: Utilization 30 + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 31 + {{- end }} 32 + {{- end }}
+42
charts/kaneo/templates/ingress.yaml
··· 1 + {{- if .Values.ingress.enabled -}} 2 + {{- $fullName := include "kaneo.fullname" . -}} 3 + apiVersion: networking.k8s.io/v1 4 + kind: Ingress 5 + metadata: 6 + name: {{ $fullName }} 7 + labels: 8 + {{- include "kaneo.labels" . | nindent 4 }} 9 + {{- with .Values.ingress.annotations }} 10 + annotations: 11 + {{- toYaml . | nindent 4 }} 12 + {{- end }} 13 + spec: 14 + {{- if .Values.ingress.className }} 15 + ingressClassName: {{ .Values.ingress.className }} 16 + {{- end }} 17 + {{- if .Values.ingress.tls }} 18 + tls: 19 + {{- range .Values.ingress.tls }} 20 + - hosts: 21 + {{- range .hosts }} 22 + - {{ . | quote }} 23 + {{- end }} 24 + secretName: {{ .secretName }} 25 + {{- end }} 26 + {{- end }} 27 + rules: 28 + {{- range .Values.ingress.hosts }} 29 + - host: {{ .host | quote }} 30 + http: 31 + paths: 32 + {{- range .paths }} 33 + - path: {{ .path }} 34 + pathType: {{ .pathType }} 35 + backend: 36 + service: 37 + name: {{ $fullName }}-{{ .service }} 38 + port: 39 + number: {{ .port }} 40 + {{- end }} 41 + {{- end }} 42 + {{- end }}
+21
charts/kaneo/templates/pvc.yaml
··· 1 + {{- if .Values.api.persistence.enabled }} 2 + apiVersion: v1 3 + kind: PersistentVolumeClaim 4 + metadata: 5 + name: {{ include "kaneo.fullname" . }}-sqlite-data 6 + labels: 7 + {{- include "kaneo.labels" . | nindent 4 }} 8 + spec: 9 + accessModes: 10 + - {{ .Values.api.persistence.accessMode | default "ReadWriteOnce" }} 11 + {{- if .Values.api.persistence.storageClass }} 12 + {{- if (eq "-" .Values.api.persistence.storageClass) }} 13 + storageClassName: "" 14 + {{- else }} 15 + storageClassName: {{ .Values.api.persistence.storageClass }} 16 + {{- end }} 17 + {{- end }} 18 + resources: 19 + requests: 20 + storage: {{ .Values.api.persistence.size | default "1Gi" }} 21 + {{- end }}
+12
charts/kaneo/templates/serviceaccount.yaml
··· 1 + {{- if .Values.serviceAccount.create -}} 2 + apiVersion: v1 3 + kind: ServiceAccount 4 + metadata: 5 + name: {{ include "kaneo.serviceAccountName" . }} 6 + labels: 7 + {{- include "kaneo.labels" . | nindent 4 }} 8 + {{- with .Values.serviceAccount.annotations }} 9 + annotations: 10 + {{- toYaml . | nindent 4 }} 11 + {{- end }} 12 + {{- end }}
+36
charts/kaneo/templates/services.yaml
··· 1 + # API Service 2 + apiVersion: v1 3 + kind: Service 4 + metadata: 5 + name: {{ include "kaneo.fullname" . }}-api 6 + labels: 7 + {{- include "kaneo.labels" . | nindent 4 }} 8 + app.kubernetes.io/component: api 9 + spec: 10 + type: {{ .Values.api.service.type }} 11 + ports: 12 + - port: {{ .Values.api.service.port }} 13 + targetPort: api 14 + protocol: TCP 15 + name: http 16 + selector: 17 + {{- include "kaneo.selectorLabels" . | nindent 4 }} 18 + 19 + --- 20 + # Web Service 21 + apiVersion: v1 22 + kind: Service 23 + metadata: 24 + name: {{ include "kaneo.fullname" . }}-web 25 + labels: 26 + {{- include "kaneo.labels" . | nindent 4 }} 27 + app.kubernetes.io/component: web 28 + spec: 29 + type: {{ .Values.web.service.type }} 30 + ports: 31 + - port: {{ .Values.web.service.port }} 32 + targetPort: web 33 + protocol: TCP 34 + name: http 35 + selector: 36 + {{- include "kaneo.selectorLabels" . | nindent 4 }}
+149
charts/kaneo/values.yaml
··· 1 + # Global values 2 + nameOverride: "" 3 + fullnameOverride: "" 4 + replicaCount: 1 5 + 6 + # Autoscaling configuration 7 + autoscaling: 8 + enabled: false 9 + minReplicas: 1 10 + maxReplicas: 10 11 + targetCPUUtilizationPercentage: 80 12 + # targetMemoryUtilizationPercentage: 80 13 + 14 + # Pod configuration 15 + podAnnotations: {} 16 + podSecurityContext: {} 17 + nodeSelector: {} 18 + tolerations: [] 19 + affinity: {} 20 + 21 + # Service account configuration 22 + serviceAccount: 23 + create: true 24 + annotations: {} 25 + name: "" 26 + 27 + # API backend configuration 28 + api: 29 + image: 30 + repository: ghcr.io/usekaneo/api 31 + tag: latest 32 + pullPolicy: IfNotPresent 33 + 34 + securityContext: {} 35 + 36 + service: 37 + type: ClusterIP 38 + port: 1337 39 + targetPort: 1337 40 + 41 + # Resources are optional and disabled by default 42 + resources: {} 43 + # resources: 44 + # limits: 45 + # cpu: 500m 46 + # memory: 512Mi 47 + # requests: 48 + # cpu: 100m 49 + # memory: 128Mi 50 + 51 + # Environment variables for the API 52 + env: 53 + jwtAccess: "change_me" 54 + existingSecret: 55 + enabled: false 56 + name: "" 57 + key: "jwt-access" 58 + 59 + # SQLite data persistence 60 + persistence: 61 + enabled: true 62 + mountPath: "/app/apps/api/data" 63 + dbFilename: "kaneo.db" 64 + accessMode: ReadWriteOnce 65 + size: 1Gi 66 + storageClass: "" 67 + 68 + livenessProbe: 69 + httpGet: 70 + path: /me 71 + port: api 72 + initialDelaySeconds: 30 73 + periodSeconds: 10 74 + 75 + readinessProbe: 76 + httpGet: 77 + path: /me 78 + port: api 79 + initialDelaySeconds: 5 80 + periodSeconds: 10 81 + 82 + # Web frontend configuration 83 + web: 84 + image: 85 + repository: ghcr.io/usekaneo/web 86 + tag: latest 87 + pullPolicy: IfNotPresent 88 + 89 + # Environment variables for the Web 90 + env: 91 + # Optional: Override the default API URL (http://localhost:1337) 92 + # The /api path will be automatically appended to the URL 93 + # apiUrl: "https://kaneo.example.com" 94 + apiUrl: "" 95 + 96 + securityContext: {} 97 + 98 + service: 99 + type: ClusterIP 100 + port: 80 101 + targetPort: 80 102 + 103 + # Resources are optional and disabled by default 104 + resources: {} 105 + # resources: 106 + # limits: 107 + # cpu: 300m 108 + # memory: 256Mi 109 + # requests: 110 + # cpu: 100m 111 + # memory: 128Mi 112 + 113 + livenessProbe: 114 + httpGet: 115 + path: / 116 + port: web 117 + initialDelaySeconds: 30 118 + periodSeconds: 10 119 + 120 + readinessProbe: 121 + httpGet: 122 + path: / 123 + port: web 124 + initialDelaySeconds: 5 125 + periodSeconds: 10 126 + 127 + # Ingress configuration 128 + ingress: 129 + enabled: false 130 + className: "nginx" 131 + annotations: 132 + nginx.ingress.kubernetes.io/rewrite-target: /$1 133 + # When using nginx ingress controller, these annotations provide URL rewriting 134 + # similar to the URLRewrite filter in the Gateway API 135 + # Other ingress controllers may require different annotations, handle carefully 136 + # because it can break the API calls from the frontend to the backend 137 + hosts: 138 + # Use the same host in the web env variable apiUrl (with http:// or https://) 139 + - host: kaneo.local 140 + paths: 141 + - path: /?(.*) 142 + pathType: Prefix 143 + service: web 144 + port: 80 145 + - path: /api/?(.*) 146 + pathType: Prefix 147 + service: api 148 + port: 1337 149 + tls: []