Self-hosted deployment
Deploy on Kubernetes
Run your own Carbone instance in your Kubernetes cluster
Introduction
Setting up a document generation solution in your Kubernetes cluster is simple.
The recommended way to deploy Carbone on Kubernetes is our official Helm chart, available on Artifact Hub. It handles the Deployment, Service, Secrets, Ingress, autoscaling and multi-instance peer discovery for you, and comes with ready-to-use values.yaml examples for AWS EKS, Azure AKS, GCP, OVH and Scaleway.
By default, the program runs with free Community features. To use Carbone Enterprise Edition, you need a Carbone license. Contact us to learn more, or request a free 30-day trial in our chat.
Quickstart with Helm
Prerequisites
- Kubernetes 1.23+
- Helm 3.8+
- An ingress controller (nginx, traefik…) if external access is required
- A persistent storage backend (S3, Azure Blob, or a
ReadWriteManyPersistentVolume) for production use
Installation
Add the Carbone Helm repository:
helm repo add carbone https://bin.carbone.io/helm/
helm repo update
Create a values.yaml file with at least your license:
applicationConfiguration:
license: "<YOUR_CARBONE_LICENSE>"
Install the chart:
helm upgrade --install carbone-ee carbone/carbone-ee \n --create-namespace -n carbone \n -f values.yaml
Then check that everything is running correctly:
kubectl get pods -n carbone
helm test carbone-ee -n carbone
The chart exposes NOTES.txt on install/upgrade, summarizing your scaling, storage, security and access configuration, and warning you about anything missing (license, storage, authentication...).
Configuration reference
The recommended approach is to maintain a values.yaml file and pass it with -f. The key parameters are listed below; see the full values.yaml on Artifact Hub for everything else.
Application
| Parameter | Description | Default |
|---|---|---|
image.tag |
Carbone image tag | chart appVersion |
image.pullPolicy |
Image pull policy | Always |
replicaCount |
Number of replicas | 4 |
applicationConfiguration.license |
Carbone EE license key | "" |
applicationConfiguration.port |
HTTP port | 4000 |
applicationConfiguration.studio |
Enable Carbone Studio UI | true |
applicationConfiguration.studioBasicAuthentication |
Basic auth for Studio (user:password) |
"" |
applicationConfiguration.authentication |
Enable JWT authentication on the API | false |
applicationConfiguration.authenticationPublicKey |
RSA public key for JWT verification | "" |
applicationConfiguration.lang |
Default locale | fr |
applicationConfiguration.timezone |
Default timezone | Europe/Paris |
applicationConfiguration.nbConvertThread |
Number of LibreOffice conversion threads per pod | 1 |
applicationConfiguration.timeoutConversion |
Conversion timeout in ms | 60000 |
applicationConfiguration.maxInputSize |
Max request body size in bytes | 62914560 |
applicationConfiguration.templateManagement |
Enable the template management API | true |
The license and public key are never written in plain YAML in the cluster: the chart stores them in a Kubernetes Secret and injects them into the pods as environment variables.
Autoscaling
| Parameter | Description | Default |
|---|---|---|
autoscaling.enabled |
Enable HorizontalPodAutoscaler | false |
autoscaling.minReplicas |
Minimum replicas | 1 |
autoscaling.maxReplicas |
Maximum replicas | 100 |
autoscaling.targetCPUUtilizationPercentage |
CPU target for scaling | 70 |
Ingress
| Parameter | Description | Default |
|---|---|---|
ingress.enabled |
Enable ingress | true |
ingress.className |
Ingress class name | "" |
ingress.annotations |
Ingress annotations | {} |
ingress.hosts |
List of hosts and paths | [{host: "", paths: [{path: /}]}] |
ingress.tls |
TLS configuration | [] |
Storage backends
A persistent storage backend is required for production: templates and renders need to survive pod restarts and be shared across replicas. Choose one of the following options in your values.yaml.
S3 (or S3-compatible)
Works with AWS S3, Scaleway Object Storage, OVH Object Storage, GCS (S3-compatible mode), MinIO, and others.
persistentStorage:
s3:
enabled: true
endpoint: s3.eu-west-1.amazonaws.com
region: eu-west-1
templatesBucket: my-carbone-templates
rendersBucket: my-carbone-renders
accessKeyId: <ACCESS_KEY_ID>
accessKeySecret: <ACCESS_KEY_SECRET>
Azure Blob Storage
persistentStorage:
azureBlobStorage:
enabled: true
storageAccount: mystorageaccount
storageKey: <STORAGE_KEY>
templatesContainer: carbone-templates
rendersContainer: carbone-renders
PersistentVolume (ReadWriteMany)
Suitable for on-premise or single-node setups. For multi-replica deployments, the volume must support ReadWriteMany (NFS is often the best solution).
persistentStorage:
persistentVolume:
enabled: true
persistentVolumeClaimName: carbone-pvc
templateFolder: templates
rendersFolder: renders
Multi-instance and high availability
When replicaCount > 1 or autoscaling.enabled: true, pods automatically discover each other via WebSocket (port 5001) and synchronize template metadata — no additional configuration is required, peer discovery is handled by the headless service.
For optimal availability with multiple replicas, consider adding topology spread constraints to your values.yaml:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: carbone-ee
topologyKey: kubernetes.io/hostname
Authentication
To enable API authentication:
- Generate a private/public Carbone key pair
The key generation tool is included in the docker image from Carbone version 5 onwards.
docker run -it --platform "linux/amd64" carbone/carbone-ee:slim-5.0.0-beta.0 generate-keys
The two keys are generated and displayed in the console. Keep the private key (key.pem) secret on your side.
- Enable authentication in values.yaml
applicationConfiguration:
authentication: true
authenticationPublicKey: |
<content of the public key>
Then upgrade your release with helm upgrade (see below). The chart stores the public key in a Kubernetes Secret and mounts it for you.
- Generate a JWT token
docker run -it --platform "linux/amd64" carbone/carbone-ee:slim-5.0.0-beta.0 generate-token
## Paste the content of key.pem in the terminal
A JWT token is then displayed in the console. You can use it in your API calls.
Upgrade and uninstall
helm upgrade carbone-ee carbone/carbone-ee -n carbone -f values.yaml
To uninstall:
helm uninstall carbone-ee -n carbone
kubectl delete namespace carbone
Manual deployment (without Helm)
If you cannot use Helm, you can deploy Carbone with plain Kubernetes manifests instead.
The example below uses a plain Deployment, which only supports simple, stateless setups. If you need template management or the job balancer (multi-pod clustering, metadata replication), Carbone pods need stable network identities to discover and stay connected to each other, which requires a StatefulSet with a headless Service instead of a Deployment. Writing and maintaining that setup by hand is significantly more involved — using the Helm chart is recommended in that case, since it already deploys a StatefulSet and wires up peer discovery for you.
Storing Carbone secrets
The first thing is to store the secrets that will be used by Carbone. In this simple example, only the license is needed, but you also need to store the public key if authentication is enabled.
To store the license :
export CARBONE_LICENSE=`cat your-license.carbone-license`
kubectl create secret generic carbone-license --from-literal=license=${CARBONE_LICENSE}
To check that it has been taken into account:
kubectl get secret
Configuring persistent volumes
Carbone requires persistent storage for templates. If multiple pods are used, render storage is also required.
These volumes must be configured as ReadWriteMany. You therefore need to choose the best implementation for your cloud provider (NFS is often the best solution).
Here is an example of yaml for the generic configuration of this volume:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: carbone-storage
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
To deploy it on the cluster :
kubectl apply -f carbone-volume.yaml
Pods deployment
Here is an example of a yaml deployment with the creation of 3 Carbone pods. This Deployment does not enable template management or the job balancer — pods run independently, each with its own template storage under subPath: template, with no metadata replication between them. It is only suitable if you don't need clustering features; otherwise switch to a StatefulSet (or use the Helm chart, see above).
apiVersion: apps/v1
kind: Deployment
metadata:
name: carbone-sample-deployment
labels:
name: carbone-ee
spec:
replicas: 3
selector:
matchLabels:
name: carbone-ee
template:
metadata:
labels:
name: carbone-ee
spec:
containers:
- name: carbone
image: "docker.io/carbone/carbone-ee:full"
resources:
requests:
cpu: 1024m
memory: 2048Mi
ports:
- containerPort: 4000
env:
- name: CARBONE_STUDIO
value: "true"
- name: CARBONE_LICENSE
valueFrom:
secretKeyRef:
name: carbone-license
key: license
volumeMounts:
- mountPath: /app/template
name: carbone-storage
subPath: template
- mountPath: /app/render
name: carbone-storage
subPath: render
livenessProbe:
httpGet:
path: /status # The path to check for the liveness probe
port: 4000 # The port to check on
initialDelaySeconds: 15 # Wait this many seconds before starting the probe
periodSeconds: 5 # Check the probe every 10 seconds
readinessProbe:
httpGet:
path: /status # The path to check for the readiness probe
port: 4000 # The port to check on
initialDelaySeconds: 5 # Wait this many seconds before starting the probe
periodSeconds: 5 # Check the probe every 5 seconds
volumes:
- name: carbone-storage
persistentVolumeClaim:
claimName: carbone-storage
To deploy it on the cluster :
kubectl apply -f carbone-simple-deployment.yaml
You can check pods status :
kubectl get pod
Service deployment
The final step is to declare the service to expose it:
kubectl expose deployment carbone-sample-deployment --type=LoadBalancer --name=carbone-service
You can check pods status :
kubectl get service
The API and Studio Carbone are now available:
http://localhost:4000Authentication
To enable API authentication, you need to follow these steps :
- Set CARBONE_AUTHENTICATION to true
- Generate private/public Carbone Key
The key generation tool is included in the docker image from Carbone version 5 onwards.
docker run -it --platform "linux/amd64" carbone/carbone-ee:slim-5.0.0-beta.0 generate-keys
The two keys will be generated and displayed in the console.
Store content of public key in new kubernetes secret and map it to /app/config/key.pub on all Carbone pods.
Create the file key.pem (with private key) and keep it secret on your side.
- Generate JWT token
Follow interactive shell :
docker run -it --platform "linux/amd64" carbone/carbone-ee:slim-5.0.0-beta.0 generate-token
## Paste in terminal content of key.pem
A JWT token is then displayed in the console. You can then use it in your API calls.
Troubleshooting
Pod status and logs.
kubectl get pods -n carbone
kubectl logs <pod-name> -n carbone
kubectl describe pod <pod-name> -n carbone
Re-check the deployment summary. helm get notes carbone-ee -n carbone re-prints the same warnings shown after install/upgrade (missing license, no persistent storage, no authentication...).
PVC stuck in Pending. The storage class backing persistentVolumeClaimName most likely doesn't support ReadWriteMany — check with kubectl get storageclass and confirm it can provision RWX volumes (see Storage backends above).
ImagePullBackOff. Verify image.repository / image.tag in your values.yaml, and that the cluster can reach Docker Hub (or configure imagePullSecrets for a private registry mirror).
Pods CrashLoopBackOff right after install. Check kubectl logs for the actual error — the most common cause is a storage backend enabled in values.yaml (S3 or Azure Blob) with an incomplete configuration (missing bucket/container name or credentials).