Add Helm charts for Kubernetes self-hosting (#87)

* Add Helm chart for Kubernetes deployment and reorganize hosting setup

- Created Helm chart for deploying Colanode on Kubernetes with PostgreSQL, Redis, and MinIO configurations
- Moved Docker Compose files to /hosting folder for better organization
- Added health check endpoints for database, Redis, and S3 services
- Introduced Dockerfile for PostgreSQL with pgvector extension to support Helm deployment
- Added GitHub Actions workflow for building and publishing Helm chart
- Updated README with Kubernetes deployment instructions

* Renames and restructure

* Update Helm chart workflow for Colanode deployment

* Remove health check route and related configurations from the server and Kubernetes deployment files.

---------

Co-authored-by: Hakan Shehu <hakanshehu15@gmail.com>
This commit is contained in:
Ylber Gashi
2025-06-25 19:06:35 +02:00
committed by GitHub
parent 035ed6b677
commit af44d9bc16
20 changed files with 985 additions and 7 deletions

View File

@@ -0,0 +1,60 @@
name: Publish Helm chart to static.colanode.com
on:
push:
branches: [main]
paths:
- 'hosting/kubernetes/chart/**'
- '.github/workflows/helm-chart-publish.yml'
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
env:
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
S3_REGION: ${{ secrets.S3_REGION }}
CHART_DIR: hosting/kubernetes/chart
steps:
- uses: actions/checkout@v4
- uses: azure/setup-helm@v3
with: { version: v3.14.3 }
- run: helm dependency update "$CHART_DIR"
- name: Configure AWS CLI
run: aws configure set default.s3.addressing_style path
- name: Package chart
run: |
mkdir -p dist
helm package "$CHART_DIR" --destination dist
- name: Fetch existing index.yaml
run: |
aws s3 cp s3://${{ secrets.S3_BUCKET }}/hosting/kubernetes/chart/index.yaml dist/index.yaml \
--endpoint-url "$S3_ENDPOINT" --region "$S3_REGION" --checksum-algorithm CRC32 || true
- name: Re-index repo
working-directory: dist
run: |
helm repo index . \
--url "https://static.colanode.com/hosting/kubernetes/chart" \
$(test -f index.yaml && echo "--merge index.yaml")
- name: Upload chart and index to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
AWS_S3_ADDRESSING_STYLE: path
run: |
aws s3 cp dist/ s3://${{ secrets.S3_BUCKET }}/hosting/kubernetes/chart/ \
--recursive \
--endpoint-url "$S3_ENDPOINT" \
--region "$S3_REGION" \
--checksum-algorithm CRC32

View File

@@ -0,0 +1,30 @@
name: Build and push custom postgresql docker image
on:
push:
branches: [main]
paths:
- 'hosting/postgresql/**'
- '.github/workflows/postgres-docker-build-and-push.yml'
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & push
uses: docker/build-push-action@v5
with:
context: ./hosting/postgresql
push: true
tags: ghcr.io/${{ github.repository_owner }}/postgresql:17

View File

@@ -39,7 +39,7 @@ All changes you make are saved to a local SQLite database first and then synced
### Concurrent edits
Colanode relies on **Conflict-free Replicated Data Types (CRDTs)** - powered by [Yjs](https://docs.yjs.dev/) - to allow real-time collaboration on entries like pages or database records. This means multiple people can edit at the same time, and the system gracefully merges everyones updates. Deletions are also tracked as specialized transactions. Messages and file operations dont support concurrent edits and use simpler database tables.
Colanode relies on **Conflict-free Replicated Data Types (CRDTs)** - powered by [Yjs](https://docs.yjs.dev/) - to allow real-time collaboration on entries like pages or database records. This means multiple people can edit at the same time, and the system gracefully merges everyone's updates. Deletions are also tracked as specialized transactions. Messages and file operations don't support concurrent edits and use simpler database tables.
## Get started for free
@@ -52,16 +52,16 @@ For optimal performance, you can install our **desktop app**, available from our
Both cloud servers are currently available in beta and free to use; pricing details will be announced soon.
### Self-host with Docker
### Self-host
If you prefer to host your own Colanode server, simply use the Docker Compose file in the root of this repository. In the near future, well provide more detailed instructions for other environments, including Kubernetes. For now, heres what you need to run Colanode yourself:
If you prefer to host your own Colanode server, check out the [`hosting/`](hosting/) folder which contains the Docker Compose file and deployment configurations. For Kubernetes deployments, see the [`hosting/kubernetes/`](hosting/kubernetes/) folder which includes Helm charts and additional documentation. Here's what you need to run Colanode yourself:
- **Postgres** with the **pgvector** extension.
- **Redis** (any Redis-compatible service will work, e.g., Valkey).
- **S3-compatible storage** (supporting basic file operations: PUT, GET, DELETE).
- **Colanode server API**, provided as a Docker image.
All required environment variables for the Colanode server can be found in the docker-compose file.
All required environment variables for the Colanode server can be found in the [`hosting/docker/docker-compose.yml`](hosting/docker/docker-compose.yml) file or [`hosting/kubernetes/README.md`](hosting/kubernetes/README.md) for Kubernetes deployments.
## License

View File

@@ -2,8 +2,8 @@ import { FastifyPluginCallback } from 'fastify';
import { accountRoutes } from '@colanode/server/api/client/routes/accounts';
import { avatarRoutes } from '@colanode/server/api/client/routes/avatars';
import { socketRoutes } from '@colanode/server/api/client/routes/sockets';
import { workspaceRoutes } from '@colanode/server/api/client/routes/workspaces';
import { socketRoutes } from '@colanode/server/api/client/routes/sockets';
export const clientRoutes: FastifyPluginCallback = (instance, _, done) => {
instance.register(socketRoutes, { prefix: '/sockets' });

View File

@@ -142,13 +142,13 @@ services:
REDIS_EVENTS_CHANNEL: 'events'
# ───────────────────────────────────────────────────────────────
# S3 configuration for files and avatars.
# S3 configuration for files.
# In the future we will support other storage providers.
# ───────────────────────────────────────────────────────────────
STORAGE_S3_ENDPOINT: 'http://minio:9000'
STORAGE_S3_ACCESS_KEY: 'minioadmin'
STORAGE_S3_SECRET_KEY: 'your_minio_password'
STORAGE_S3_BUCKET: 'colanode-avatars'
STORAGE_S3_BUCKET: 'colanode'
STORAGE_S3_REGION: 'us-east-1'
STORAGE_S3_FORCE_PATH_STYLE: 'true'

View File

@@ -0,0 +1,107 @@
# Colanode Kubernetes Deployment
A Helm chart for deploying [Colanode](https://github.com/colanode/colanode) on Kubernetes.
## Overview
This chart deploys a complete Colanode instance with all required dependencies:
- **Colanode Server**: The main application server
- **PostgreSQL**: Database with pgvector extension for vector operations
- **Redis/Valkey**: Message queue and caching
- **MinIO**: S3-compatible object storage for files and avatars
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- Ingress controller (if ingress is enabled)
## Installation
### Quick Start
```bash
# Add the chart repository (if publishing to a Helm repo)
helm repo add colanode https://static.colanode.com/hosting/kubernetes/chart
# Install with default values
helm install my-colanode colanode/colanode
# Or install from local chart
helm install my-colanode ./hosting/kubernetes/chart
```
### Custom Installation
```bash
# Install with custom values
helm install my-colanode ./hosting/kubernetes/chart \
--set colanode.ingress.hosts[0].host=colanode.example.com \
--set colanode.config.SERVER_NAME="My Colanode Instance"
```
## Configuration
### Core Settings
| Parameter | Description | Default |
| ----------------------------- | --------------------------- | ------------------------- |
| `colanode.replicaCount` | Number of Colanode replicas | `1` |
| `colanode.image.repository` | Colanode image repository | `ghcr.io/colanode/server` |
| `colanode.image.tag` | Colanode image tag | `latest` |
| `colanode.config.SERVER_NAME` | Server display name | `Colanode K8s` |
### Ingress Configuration
| Parameter | Description | Default |
| -------------------------------- | ------------------------ | --------------------- |
| `colanode.ingress.enabled` | Enable ingress | `true` |
| `colanode.ingress.hosts[0].host` | Hostname for the ingress | `chart-example.local` |
| `colanode.ingress.className` | Ingress class name | `""` |
### Dependencies
| Parameter | Description | Default |
| -------------------- | ---------------------------- | ------- |
| `postgresql.enabled` | Enable PostgreSQL deployment | `true` |
| `redis.enabled` | Enable Redis deployment | `true` |
| `minio.enabled` | Enable MinIO deployment | `true` |
## Important Notes
### Security Settings
The chart includes `global.security.allowInsecureImages: true` because we use a custom PostgreSQL image with the pgvector extension. This setting is required by Bitnami Helm charts when using non-official images.
### Storage
By default, the chart configures persistent storage for:
- PostgreSQL: 8Gi
- Redis: 8Gi
- MinIO: 10Gi
Adjust these values based on your requirements.
## Accessing Colanode
After installation, you can access Colanode through:
1. **Ingress** (recommended): Configure your ingress host and access via HTTP/HTTPS
2. **Port forwarding**: `kubectl port-forward svc/my-colanode 3000:3000`
3. **LoadBalancer**: Change service type to LoadBalancer if supported by your cluster
## Uninstall
```bash
helm uninstall my-colanode
```
## Development
To modify the chart:
1. Edit values in `/hosting/kubernetes/chart/values.yaml`
2. Update templates in `/hosting/kubernetes/chart/templates/`
3. Test with `helm template` or `helm install --dry-run`

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,21 @@
apiVersion: v2
name: colanode
description: A Helm chart for Colanode - open-source & local-first collaboration workspace
type: application
version: 0.1.0
appVersion: '1.0.0'
dependencies:
- name: postgresql
version: '16.7.4'
repository: 'https://charts.bitnami.com/bitnami'
condition: postgresql.enabled
- name: valkey
version: '3.0.4'
repository: 'https://charts.bitnami.com/bitnami'
alias: redis
condition: redis.enabled
- name: minio
version: '16.0.10'
repository: 'https://charts.bitnami.com/bitnami'
condition: minio.enabled

View File

@@ -0,0 +1,63 @@
Thank you for installing {{ .Chart.Name }}!
Your release is named {{ .Release.Name }}.
═══════════════════════════════════════════════════════════════
ACCESSING COLANODE
═══════════════════════════════════════════════════════════════
{{- if .Values.colanode.ingress.enabled }}
Your Colanode instance will be available at:
{{- range .Values.colanode.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.colanode.ingress.tls }}s{{ end }}://{{ .host }}{{ if ne .path "/" }}{{ .path }}{{ end }}
{{- end }}
{{- end }}
Note: Make sure your ingress controller is configured and DNS is pointing to your cluster.
{{- else }}
To access Colanode, run:
kubectl port-forward svc/{{ include "colanode.fullname" . }} 3000:3000 -n {{ .Release.Namespace }}
Then visit: http://localhost:3000
{{- end }}
═══════════════════════════════════════════════════════════════
DESKTOP APPLICATION
═══════════════════════════════════════════════════════════════
Download the Colanode desktop app from: https://colanode.com/downloads
Use the server URL above when connecting.
═══════════════════════════════════════════════════════════════
DEPLOYMENT STATUS
═══════════════════════════════════════════════════════════════
Check deployment status:
kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }}
View application logs:
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "colanode.name" . }} -f
═══════════════════════════════════════════════════════════════
SERVICES INFORMATION
═══════════════════════════════════════════════════════════════
{{- if .Values.postgresql.enabled }}
📊 PostgreSQL (with pgvector): {{ include "colanode.postgresql.hostname" . }}:5432
{{- end }}
{{- if .Values.redis.enabled }}
🔄 Redis/Valkey: {{ include "colanode.redis.hostname" . }}:6379
{{- end }}
{{- if .Values.minio.enabled }}
💾 MinIO Storage: {{ include "colanode.minio.hostname" . }}:9000
Console: {{ include "colanode.minio.hostname" . }}:9001
{{- end }}
All service credentials are auto-generated and stored in Kubernetes secrets.
To retrieve them: kubectl get secrets -n {{ .Release.Namespace }}
═══════════════════════════════════════════════════════════════
For more information, visit: https://github.com/colanode/colanode
For support, create an issue at: https://github.com/colanode/colanode/issues

View File

@@ -0,0 +1,279 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "colanode.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "colanode.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "colanode.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "colanode.labels" -}}
helm.sh/chart: {{ include "colanode.chart" . }}
{{ include "colanode.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "colanode.selectorLabels" -}}
app.kubernetes.io/name: {{ include "colanode.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "colanode.serviceAccountName" -}}
{{- if .Values.colanode.serviceAccount.create }}
{{- default (include "colanode.fullname" .) .Values.colanode.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.colanode.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Return the PostgreSQL hostname
*/}}
{{- define "colanode.postgresql.hostname" -}}
{{- printf "%s-postgresql" .Release.Name -}}
{{- end }}
{{/*
Return the Redis hostname
*/}}
{{- define "colanode.redis.hostname" -}}
{{- printf "%s-redis-primary" .Release.Name -}}
{{- end }}
{{/*
Return the MinIO hostname
*/}}
{{- define "colanode.minio.hostname" -}}
{{- printf "%s-minio" .Release.Name -}}
{{- end }}
{{/*
Helper to get value from secret key reference or direct value
Usage: {{ include "colanode.getValueOrSecret" (dict "key" "theKey" "value" .Values.path.to.value) }}
*/}}
{{- define "colanode.getValueOrSecret" -}}
{{- $value := .value -}}
{{- if and $value.existingSecret $value.secretKey -}}
valueFrom:
secretKeyRef:
name: {{ $value.existingSecret }}
key: {{ $value.secretKey }}
{{- else if hasKey $value "value" -}}
value: {{ $value.value | quote }}
{{- end -}}
{{- end }}
{{/*
Helper to get required value from secret key reference or direct value
Usage: {{ include "colanode.getRequiredValueOrSecret" (dict "key" "theKey" "value" .Values.path.to.value) }}
*/}}
{{- define "colanode.getRequiredValueOrSecret" -}}
{{- $value := .value -}}
{{- if and $value.existingSecret $value.secretKey -}}
valueFrom:
secretKeyRef:
name: {{ $value.existingSecret }}
key: {{ $value.secretKey }}
{{- else if hasKey $value "value" -}}
value: {{ $value.value | quote }}
{{- else -}}
{{ fail (printf "A value or a secret reference for key '%s' is required." .key) }}
{{- end -}}
{{- end }}
{{/*
Colanode Server Environment Variables
*/}}
{{- define "colanode.serverEnvVars" -}}
# ───────────────────────────────────────────────────────────────
# General Node/Server Config
# ───────────────────────────────────────────────────────────────
- name: NODE_ENV
value: {{ .Values.colanode.config.NODE_ENV | quote }}
- name: PORT
value: {{ .Values.colanode.service.port | quote }}
- name: SERVER_NAME
value: {{ .Values.colanode.config.SERVER_NAME | quote }}
- name: SERVER_AVATAR
value: {{ .Values.colanode.config.SERVER_AVATAR | quote }}
- name: SERVER_MODE
value: {{ .Values.colanode.config.SERVER_MODE | quote }}
# ───────────────────────────────────────────────────────────────
# Account Configuration
# ───────────────────────────────────────────────────────────────
- name: ACCOUNT_VERIFICATION_TYPE
value: {{ .Values.colanode.config.ACCOUNT_VERIFICATION_TYPE | quote }}
- name: ACCOUNT_OTP_TIMEOUT
value: {{ .Values.colanode.config.ACCOUNT_OTP_TIMEOUT | quote }}
- name: ACCOUNT_ALLOW_GOOGLE_LOGIN
value: {{ .Values.colanode.config.ACCOUNT_ALLOW_GOOGLE_LOGIN | quote }}
# ───────────────────────────────────────────────────────────────
# User Configuration
# ───────────────────────────────────────────────────────────────
- name: USER_STORAGE_LIMIT
value: {{ .Values.colanode.config.USER_STORAGE_LIMIT | quote }}
- name: USER_MAX_FILE_SIZE
value: {{ .Values.colanode.config.USER_MAX_FILE_SIZE | quote }}
# ───────────────────────────────────────────────────────────────
# PostgreSQL Configuration
# ───────────────────────────────────────────────────────────────
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgresql
key: postgres-password
- name: POSTGRES_URL
value: "postgres://{{ .Values.postgresql.auth.username }}:$(POSTGRES_PASSWORD)@{{ include "colanode.postgresql.hostname" . }}:5432/{{ .Values.postgresql.auth.database }}"
# ───────────────────────────────────────────────────────────────
# Redis/Valkey Configuration
# ───────────────────────────────────────────────────────────────
- name: REDIS_PASSWORD
{{- if .Values.redis.auth.existingSecret }}
{{- include "colanode.getRequiredValueOrSecret" (dict
"key" "redis.auth.password"
"value" (dict
"value" .Values.redis.auth.password
"existingSecret" .Values.redis.auth.existingSecret
"secretKey" .Values.redis.auth.secretKeys.redisPasswordKey )) | nindent 2 }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-redis
key: {{ .Values.redis.auth.secretKeys.redisPasswordKey }}
{{- end }}
- name: REDIS_URL
value: "redis://:$(REDIS_PASSWORD)@{{ include "colanode.redis.hostname" . }}:6379/{{ .Values.colanode.config.REDIS_DB }}"
- name: REDIS_DB
value: {{ .Values.colanode.config.REDIS_DB | quote }}
- name: REDIS_JOBS_QUEUE_NAME
value: {{ .Values.colanode.config.REDIS_JOBS_QUEUE_NAME | quote }}
- name: REDIS_JOBS_QUEUE_PREFIX
value: {{ .Values.colanode.config.REDIS_JOBS_QUEUE_PREFIX | quote }}
- name: REDIS_EVENTS_CHANNEL
value: {{ .Values.colanode.config.REDIS_EVENTS_CHANNEL | quote }}
# ───────────────────────────────────────────────────────────────
# S3 Configuration for Avatars
# ───────────────────────────────────────────────────────────────
- name: S3_AVATARS_ENDPOINT
value: "http://{{ include "colanode.minio.hostname" . }}:9000"
- name: S3_AVATARS_ACCESS_KEY
{{- if .Values.minio.auth.existingSecret }}
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "minio.auth.rootUser" "value" (dict "value" .Values.minio.auth.rootUser "existingSecret" .Values.minio.auth.existingSecret "secretKey" .Values.minio.auth.rootUserKey )) | nindent 2 }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-minio
key: {{ .Values.minio.auth.rootUserKey }}
{{- end }}
- name: S3_AVATARS_SECRET_KEY
{{- if .Values.minio.auth.existingSecret }}
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "minio.auth.rootPassword" "value" (dict "value" .Values.minio.auth.rootPassword "existingSecret" .Values.minio.auth.existingSecret "secretKey" .Values.minio.auth.rootPasswordKey )) | nindent 2 }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-minio
key: {{ .Values.minio.auth.rootPasswordKey }}
{{- end }}
- name: S3_AVATARS_BUCKET_NAME
value: "colanode-avatars"
- name: S3_AVATARS_REGION
value: "us-east-1" # Region is often optional for MinIO but good practice
- name: S3_AVATARS_FORCE_PATH_STYLE
value: "true"
# ───────────────────────────────────────────────────────────────
# S3 Configuration for Files
# ───────────────────────────────────────────────────────────────
- name: S3_FILES_ENDPOINT
value: "http://{{ include "colanode.minio.hostname" . }}:9000"
- name: S3_FILES_ACCESS_KEY
{{- if .Values.minio.auth.existingSecret }}
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "minio.auth.rootUser" "value" (dict "value" .Values.minio.auth.rootUser "existingSecret" .Values.minio.auth.existingSecret "secretKey" .Values.minio.auth.rootUserKey )) | nindent 2 }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-minio
key: {{ .Values.minio.auth.rootUserKey }}
{{- end }}
- name: S3_FILES_SECRET_KEY
{{- if .Values.minio.auth.existingSecret }}
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "minio.auth.rootPassword" "value" (dict "value" .Values.minio.auth.rootPassword "existingSecret" .Values.minio.auth.existingSecret "secretKey" .Values.minio.auth.rootPasswordKey )) | nindent 2 }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-minio
key: {{ .Values.minio.auth.rootPasswordKey }}
{{- end }}
- name: S3_FILES_BUCKET_NAME
value: "colanode-files"
- name: S3_FILES_REGION
value: "us-east-1"
- name: S3_FILES_FORCE_PATH_STYLE
value: "true"
# ───────────────────────────────────────────────────────────────
# SMTP configuration
# ───────────────────────────────────────────────────────────────
- name: SMTP_ENABLED
value: {{ .Values.colanode.config.SMTP_ENABLED | quote }}
{{- if eq .Values.colanode.config.SMTP_ENABLED "true" }}
- name: SMTP_HOST
value: {{ required "colanode.config.SMTP_HOST must be set when SMTP_ENABLED is true" .Values.colanode.config.SMTP_HOST | quote }}
- name: SMTP_PORT
value: {{ required "colanode.config.SMTP_PORT must be set when SMTP_ENABLED is true" .Values.colanode.config.SMTP_PORT | quote }}
- name: SMTP_USER
value: {{ .Values.colanode.config.SMTP_USER | quote }}
- name: SMTP_PASSWORD
value: {{ .Values.colanode.config.SMTP_PASSWORD | quote }}
- name: SMTP_EMAIL_FROM
value: {{ required "colanode.config.SMTP_EMAIL_FROM must be set when SMTP_ENABLED is true" .Values.colanode.config.SMTP_EMAIL_FROM | quote }}
- name: SMTP_EMAIL_FROM_NAME
value: {{ .Values.colanode.config.SMTP_EMAIL_FROM_NAME | quote }}
{{- end }}
# ───────────────────────────────────────────────────────────────
# AI Configuration
# ───────────────────────────────────────────────────────────────
- name: AI_ENABLED
value: {{ .Values.colanode.config.AI_ENABLED | quote }}
{{- end }}

View File

@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "colanode.fullname" . }}
labels:
{{- include "colanode.labels" . | nindent 4 }}
spec:
{{- if not .Values.colanode.autoscaling.enabled }}
replicas: {{ .Values.colanode.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "colanode.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.colanode.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "colanode.selectorLabels" . | nindent 8 }}
{{- with .Values.colanode.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.colanode.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "colanode.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.colanode.image.repository }}:{{ .Values.colanode.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.colanode.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.colanode.service.port }}
protocol: TCP
env:
{{- include "colanode.serverEnvVars" . | nindent 12 }}
resources:
{{- toYaml .Values.colanode.resources | nindent 12 }}

View File

@@ -0,0 +1,32 @@
{{- if .Values.colanode.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "colanode.fullname" . }}
labels:
{{- include "colanode.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "colanode.fullname" . }}
minReplicas: {{ .Values.colanode.autoscaling.minReplicas }}
maxReplicas: {{ .Values.colanode.autoscaling.maxReplicas }}
metrics:
{{- if .Values.colanode.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.colanode.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.colanode.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.colanode.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,59 @@
{{- if .Values.colanode.ingress.enabled -}}
{{- if and .Values.colanode.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.colanode.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.colanode.ingress.annotations "kubernetes.io/ingress.class" .Values.colanode.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ include "colanode.fullname" . }}
labels:
{{- include "colanode.labels" . | nindent 4 }}
{{- with .Values.colanode.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.colanode.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.colanode.ingress.className }}
{{- end }}
{{- if .Values.colanode.ingress.tls }}
tls:
{{- range .Values.colanode.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.colanode.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ include "colanode.fullname" $ }}
port:
name: http
{{- else }}
serviceName: {{ include "colanode.fullname" $ }}
servicePort: http
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,12 @@
{{- if and .Values.minio.enabled .Values.minio.auth.rootPassword (not .Values.minio.auth.existingSecret) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ template "colanode.fullname" . }}-minio
labels:
{{- include "colanode.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.minio.auth.rootUserKey }}: {{ .Values.minio.auth.rootUser | b64enc | quote }}
{{ .Values.minio.auth.rootPasswordKey }}: {{ .Values.minio.auth.rootPassword | b64enc | quote }}
{{- end }}

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "colanode.fullname" . }}
labels: {{- include "colanode.labels" . | nindent 4 }}
spec:
type: {{ .Values.colanode.service.type }}
ports:
- port: {{ .Values.colanode.service.port }}
targetPort: http
protocol: TCP
name: http
selector: {{- include "colanode.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.colanode.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "colanode.serviceAccountName" . }}
labels:
{{- include "colanode.labels" . | nindent 4 }}
{{- with .Values.colanode.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.colanode.serviceAccount.automount }}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{- if and .Values.redis.enabled .Values.redis.auth.enabled .Values.redis.auth.password (not .Values.redis.auth.existingSecret) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ template "colanode.fullname" . }}-redis
labels:
{{- include "colanode.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.redis.auth.secretKeys.redisPasswordKey }}: {{ .Values.redis.auth.password | b64enc | quote }}
{{- end }}

View File

@@ -0,0 +1,193 @@
# Default values for colanode.
# -- Override the name for the selector labels, defaults to the chart name
nameOverride: ''
# -- Override the full name of the deployed resources, defaults to a combination of the release name and the name for the selector labels
fullnameOverride: ''
# Core Colanode Configuration
colanode:
# -- Number of replicas for the Colanode server deployment
replicaCount: 1
image:
# -- Colanode server image repository
repository: ghcr.io/colanode/server
# -- Colanode server image pull policy
pullPolicy: Always
# -- Colanode server image tag
tag: 'latest'
# -- Image pull secrets
imagePullSecrets: []
serviceAccount:
# -- Specifies whether a service account should be created
create: true
# -- Annotations to add to the service account
annotations: {}
# -- The name of the service account to use. If not set and create is true, a name is generated using the fullname template
name: ''
# -- Automatically mount a ServiceAccount's API credentials?
automount: true
# -- Pod annotations (empty by default)
podAnnotations: {}
# -- Pod labels (empty by default)
podLabels: {}
service:
# -- Kubernetes service type
type: ClusterIP
# -- Kubernetes service port
port: 3000
ingress:
# -- Enable ingress controller resource
enabled: false
# -- Ingress controller class name
className: ''
# -- Ingress annotations
annotations:
{}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# -- Ingress hostnames
hosts:
- host: chart-example.local
paths:
- path: /
pathType: Prefix
# -- Ingress TLS configuration
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
# -- Resource requests and limits (empty by default for flexibility)
resources: {}
# -- Horizontal Pod Autoscaler (disabled by default for simplicity)
autoscaling:
enabled: false
# -- Colanode server configuration
config:
# Core settings
NODE_ENV: production
PORT: 3000
SERVER_NAME: 'Colanode Kubernetes'
SERVER_AVATAR: ''
SERVER_MODE: 'standalone'
# Debug logging (uncomment to enable verbose logs)
DEBUG: 'colanode:*'
# Account settings
ACCOUNT_VERIFICATION_TYPE: 'automatic'
ACCOUNT_OTP_TIMEOUT: '600' # in seconds
ACCOUNT_ALLOW_GOOGLE_LOGIN: 'false'
# User limits
USER_STORAGE_LIMIT: '10737418240' # 10 GB
USER_MAX_FILE_SIZE: '104857600' # 100 MB
# Database connection (PostgreSQL)
POSTGRES_URL: 'postgres://colanode_user:$(POSTGRES_PASSWORD)@{{ .Release.Name }}-postgresql:5432/colanode_db'
# Optional SSL settings for PostgreSQL
# POSTGRES_SSL_REJECT_UNAUTHORIZED: "false"
# POSTGRES_SSL_CA: ""
# POSTGRES_SSL_KEY: ""
# POSTGRES_SSL_CERT: ""
# Redis/Valkey configuration
REDIS_DB: '0'
REDIS_JOBS_QUEUE_NAME: 'jobs'
REDIS_JOBS_QUEUE_PREFIX: 'colanode'
REDIS_EVENTS_CHANNEL: 'events'
# S3 storage for files
STORAGE_S3_ENDPOINT: 'http://{{ .Release.Name }}-minio:9000'
STORAGE_S3_ACCESS_KEY: 'minioadmin'
STORAGE_S3_SECRET_KEY: '$(MINIO_ROOT_PASSWORD)'
STORAGE_S3_BUCKET: 'colanode'
STORAGE_S3_REGION: 'us-east-1'
STORAGE_S3_FORCE_PATH_STYLE: 'true'
# Email configuration
SMTP_ENABLED: 'false'
# SMTP_HOST: ""
# SMTP_PORT: "587"
# SMTP_USER: ""
# SMTP_PASSWORD: ""
# SMTP_EMAIL_FROM: ""
# SMTP_EMAIL_FROM_NAME: "Colanode"
# AI features (optional)
AI_ENABLED: 'false'
global:
security:
# Required for custom PostgreSQL image with pgvector extension
# Bitnami Helm charts require this setting for non-official images
allowInsecureImages: true
# PostgreSQL with pgvector extension
postgresql:
# -- Enable PostgreSQL deployment
enabled: true
image:
registry: ghcr.io
repository: colanode/postgresql
tag: '17'
pullPolicy: Always
auth:
username: postgres
# password: "" # Leave empty to auto-generate
# existingSecret: "" # Will use auto-generated secret
database: colanode_db
secretKeys:
userPasswordKey: 'postgres-password'
primary:
persistence:
enabled: true
size: 8Gi
# Redis (alias for Valkey)
redis:
# -- Enable Redis deployment
enabled: true
architecture: standalone
auth:
enabled: true
username: default
# password: "" # Leave empty to auto-generate
# existingSecret: "" # Will use auto-generated secret
secretKeys:
redisPasswordKey: 'valkey-password'
master:
persistence:
enabled: true
size: 8Gi
# MinIO object storage
minio:
auth:
rootUser: 'minioadmin'
# password: "" # Leave empty to auto-generate
# existingSecret: "" # Will use auto-generated secret
rootUserKey: 'root-user'
# -- Key within the existing secret that holds the root password
rootPasswordKey: 'root-password'
persistence:
enabled: true
size: 10Gi
service:
type: ClusterIP
# Create default buckets for Colanode
defaultBuckets: 'colanode'
extraArgs:
- '--console-address'
- ':9001'

View File

@@ -0,0 +1,11 @@
# ── Stage 1: pgvector ────────────────────────────────
FROM pgvector/pgvector:pg17 AS builder
# ── Stage 2: Bitnami PostgreSQL ──────────────────────────────
FROM bitnami/postgresql:17
# Copy pgvector extension to the PostgreSQL container
COPY --from=builder /usr/lib/postgresql/17/lib/vector.so \
/opt/bitnami/postgresql/lib/
COPY --from=builder /usr/share/postgresql/17/extension/*vector* \
/opt/bitnami/postgresql/share/extension/

View File

@@ -0,0 +1,9 @@
# PostgreSQL with pgvector
Custom PostgreSQL image with pgvector extension for Colanode's vector search capabilities.
## Why Custom Image?
The default Bitnami PostgreSQL image used by our Helm chart dependency doesn't include pgvector. This custom image builds on the same base image Bitnami uses but adds the pgvector extension.
See the [Helm chart documentation](../kubernetes/README.md) for deployment details.