Migrate from VM to Kubernetes
This guide walks you through migrating your existing VM-based Kasm deployment to Kubernetes.
Ensure your VM deployment is on Kasm 1.19.0 before migrating. If you are on an older version, upgrade your VM deployment first.
Scope: This guide covers the migration of the Kasm control plane, including primary zone dedicated and connection proxies. Agents and additional zone dedicated and connection proxies are not part of the Helm chart and must be migrated or reinstalled separately after the control plane is up.
Choose the section that matches your current database setup:
- Included DB — your VM uses Kasm's built-in PostgreSQL container (
database.standalone=false) - Standalone DB — your VM connects to an external PostgreSQL database (
database.standalone=true)
Migrating with Included DB
Use this path if your VM deployment uses Kasm's built-in PostgreSQL container.
This migration requires downtime. Your existing Kasm deployment will be unavailable during the process. Plan accordingly and schedule a maintenance window.
At a Glance
- Back up the database on the VM
- Preserve your Kasm secrets
- Transfer the backup to Kubernetes
- Install the Kasm Helm chart
- Confirm the DB is running
- Restore the database
- Verify and log in
1. Back Up the Database on the VM
Follow the official Kasm documentation to take a database backup on your VM:
Rename the backup file to kasm_dump.tar once complete.
2. Preserve Your Kasm Secrets
To retain the same credentials after migration, create the Kasm secrets in your target namespace before installing the chart.
Copy the file examples/kasm-secrets.yaml from our chart source and populate it with the credential values from your VM deployment:
The secret name must be {RELEASE_NAME}-secrets, where {RELEASE_NAME} is the name you will use in your helm install command. Update the name field in kasm-secrets.yaml accordingly before applying.
All secret values must be base64 encoded.
Encode a value with: echo -n {secret-value} | base64
kubectl create namespace {NAMESPACE}
kubectl apply -f kasm-secrets.yaml -n {NAMESPACE}
Verify the values are correct:
kubectl -n {NAMESPACE} get secrets/{RELEASE_NAME}-secrets \
--template='{{ range $key, $value := .data }}{{ printf "%s: %s\n" $key ($value | base64decode) }}{{ end }}'
3. Transfer the Backup to Kubernetes
Download db-upload.yaml and update the following fields before applying:
| Field | Value |
|---|---|
image | Replace with your current VM Kasm version, e.g. kasmweb/api:1.19.0 |
storageClassName | Commented out by default to use the namespace default. Uncomment and set a value to use a specific storage class |
claimName | PVC name for the backup. Keep default (kasm-db-dump-pvc) unless you need a custom name — if changed, update claimName in db-restore.yaml to match |
Deploy the upload pod to create a PVC and receive the backup file:
kubectl apply -f db-upload.yaml -n {NAMESPACE}
Copy your backup file into the pod:
kubectl cp /path/to/kasm_dump.tar {NAMESPACE}/{UPLOAD_POD_NAME}:/data/kasm-db-dump/kasm_dump.tar
Replace {UPLOAD_POD_NAME} with the actual pod name. Retrieve it with:
kubectl get pods -n {NAMESPACE}
Monitor the upload pod logs to confirm the transfer is complete:
kubectl logs -f {UPLOAD_POD_NAME} -n {NAMESPACE}
Expected output:
Waiting for DB file upload...
File uploading.
⏳ Upload in progress... (size: 150240768 bytes)
✅ File upload complete. Final size: 206191616 bytes
Verify the Final size matches the size of your local backup file exactly before proceeding.
4. Install the Kasm Helm Chart
Install your TLS certificate into the namespace before installing the chart. Refer to Configure TLS Certificates for Kasm on Kubernetes for the available methods. Note the secret name you create — you will need it in my-values.yaml below.
Create your my-values.yaml. Refer to the chart documentation for all available configuration options, including ingress settings. At minimum, include:
publicAddr: kasm.contoso.com
certificate:
secretName: {CERTIFICATE_SECRET_NAME} # The TLS secret name you created above
dbManagement:
initialize: false
Set dbManagement.initialize: false. Enabling it will pre-populate the schema and conflict with the restore in Step 6.
Install the chart:
The {RELEASE_NAME} used here must match the secret name you applied in the previous step. For example, if your secret is named kasm-secrets, your release name must be kasm.
OCI Registry (Recommended)
helm install {RELEASE_NAME} oci://registry-1.docker.io/kasmweb/kasm-helm \
--version 1.1190.0 -n {NAMESPACE} -f my-values.yaml
Classic Helm Repository
helm repo add kasm https://helm.kasm.com
helm repo update
helm install {RELEASE_NAME} kasm/kasm-helm --version 1.1190.0 -n {NAMESPACE} -f my-values.yaml
5. Confirm the DB is Running
Watch the pods until the DB is ready:
kubectl get pods -n {NAMESPACE} --watch
Example output:
kasm-api-default-7985f66df6-pnhng 0/1 Init:0/1 0 53s
kasm-db-1-19-0-0 1/1 Running 0 52s
kasm-guac-6d44dd8844-zmpzv 0/1 Init:0/1 0 53s
kasm-manager-default-585f45f8df-h8n6z 0/1 Init:0/2 0 53s
kasm-proxy-default-9c98f99f8-k69wt 0/1 Init:0/4 0 52s
kasm-rdp-gateway-f9996c885-n74h5 0/1 Init:0/1 0 52s
kasm-rdp-https-gateway-6f5d456b59-67hxj 0/1 Init:0/1 0 52s
Once the {RELEASE_NAME}-db-* pod shows Running, press Ctrl+C to stop watching and proceed to the next step.
6. Restore the Database
Download db-restore.yaml and update the following fields:
| Field | Value |
|---|---|
image | Replace with your current VM Kasm version, e.g. kasmweb/api:1.19.0 |
POSTGRES_HOST | DB service name. Run kubectl -n {NAMESPACE} get service to find it |
POSTGRES_DB | Defaults to kasm. Only change if you set database.kasmDbName in my-values.yaml |
POSTGRES_USER | Defaults to kasmapp. Only change if you set database.kasmDbUser in my-values.yaml |
secretKeyRef.name | Must be {RELEASE_NAME}-secrets, where {RELEASE_NAME} is your Helm release name |
secretKeyRef.key | Defaults to db-password — auto-generated by the Helm chart. Keep default |
claimName | Defaults to kasm-db-dump-pvc. Keep default unless you changed it in db-upload.yaml |
Apply the restore job:
kubectl apply -n {NAMESPACE} -f db-restore.yaml
kubectl logs -f job/kasm-db-restore -n {NAMESPACE}
Expected output when the restore completes:
Restoring database from /data/kasm-db-dump/kasm_dump.tar
Restore complete
7. Verify and Log In
After the restore completes, the pods will gradually come into a running state. Watch until all pods are running:
kubectl get pods -n {NAMESPACE} --watch
Once all pods are running, press Ctrl+C to stop watching.
If you enabled ingress in your my-values.yaml, update your DNS record for publicAddr to point to the ingress address. Retrieve it with:
kubectl get ingress -n {NAMESPACE}
Use the value in the ADDRESS column. Once DNS has propagated, access your Kasm environment using the publicAddr you set. For credentials, run:
helm get notes {RELEASE_NAME} -n {NAMESPACE}
Migrating with Standalone DB
Use this path if your VM deployment connects to an external PostgreSQL database. Since your database is not managed by Kasm, it will continue running independently.
This migration requires downtime. Your existing Kasm deployment will be unavailable during the process. Plan accordingly and schedule a maintenance window.
At a Glance
1. Back Up the Database
Follow the official Kasm documentation to take a backup of your standalone PostgreSQL database:
Store the backup in a safe location before proceeding.
Once the backup is complete, stop all Kasm services on the VM to prevent any further writes to the database:
sudo /opt/kasm/current/bin/stop
2. Preserve Your Kasm Secrets
To retain the same credentials after migration, create the Kasm secrets in your target namespace before installing the chart.
Edit and apply kasm-secrets.yaml with the credential values from your VM deployment:
The secret name must be {RELEASE_NAME}-secrets, where {RELEASE_NAME} is the name you will use in your helm install command. Update the name field in kasm-secrets.yaml accordingly before applying.
All secret values must be base64 encoded.
Encode a value with: echo -n {secret-value} | base64
kubectl create namespace {NAMESPACE}
kubectl apply -f kasm-secrets.yaml -n {NAMESPACE}
Verify the values are correct:
kubectl -n {NAMESPACE} get secrets/{RELEASE_NAME}-secrets \
--template='{{ range $key, $value := .data }}{{ printf "%s: %s\n" $key ($value | base64decode) }}{{ end }}'
3. Install the Kasm Helm Chart
Install your TLS certificate into the namespace before installing the chart. Refer to Configure TLS Certificates for Kasm on Kubernetes for the available methods. Note the secret name you create — you will need it in my-values.yaml below.
Create your my-values.yaml. Refer to the chart documentation for all available configuration options, including ingress settings. At minimum, include:
publicAddr: kasm.contoso.com
certificate:
secretName: {CERTIFICATE_SECRET_NAME} # The TLS secret name you created above
database:
standalone: true
hostname: "{DB_HOSTNAME}" # <-- REPLACE with your standalone DB hostname
port: 5432 # Change if your PostgreSQL instance uses a non-default port
kasmDbName: kasm # Default VM installation uses "kasm". Change if you used a different database name
kasmDbUser: kasmapp # Default VM installation uses "kasmapp". Change if you used a different database user
dbManagement:
initialize: false
Install the chart:
The {RELEASE_NAME} used here must match the secret name you applied in the previous step. For example, if your secret is named kasm-secrets, your release name must be kasm.
OCI Registry (Recommended)
helm install {RELEASE_NAME} oci://registry-1.docker.io/kasmweb/kasm-helm \
--version 1.1190.0 -n {NAMESPACE} -f my-values.yaml
Classic Helm Repository
helm repo add kasm https://helm.kasm.com
helm repo update
helm install {RELEASE_NAME} kasm/kasm-helm --version 1.1190.0 -n {NAMESPACE} -f my-values.yaml
4. Verify and Log In
The pods will gradually come into a running state. Watch until all pods are running:
kubectl get pods -n {NAMESPACE} --watch
Once all pods are running, press Ctrl+C to stop watching.
If you enabled ingress in your my-values.yaml, update your DNS record for publicAddr to point to the ingress address. Retrieve it with:
kubectl get ingress -n {NAMESPACE}
Use the value in the ADDRESS column. Once DNS has propagated, access your Kasm environment using the publicAddr you set. For credentials, run:
helm get notes {RELEASE_NAME} -n {NAMESPACE}
Troubleshooting
See Kubernetes Troubleshooting for assistance.