A Helm chart for deploying Devolutions Server (DVLS) on Kubernetes.
Devolutions Server is a self-hosted privileged access management (PAM) solution for managing passwords, credentials, and privileged accounts.
- Automatic Database Migrations: Pre-upgrade hooks scale down the deployment, run migrations, then deploy the new version
- Environment-specific Values: Use separate value files per environment for clean configuration management
- TLS Support: Integration with cert-manager for automatic certificate management
- Gateway API / Istio Integration: Optional HTTPRoute and DestinationRule for service mesh setups
- Kubernetes 1.32+ (tested with 1.34)
- Helm 3.17+ or v4
- A SQL Server database (Azure SQL or self-hosted)
- A TLS certificate — DVLS serves HTTPS only. Use cert-manager for automatic management, or provide a pre-existing TLS secret via
certificate.secretName - Gateway API controller (optional, for HTTPRoute ingress)
- Istio (optional, for DestinationRule TLS origination)
helm repo add devolutions https://devolutions.github.io/helm-charts
helm repo updateThe chart is published in three release channels:
| Channel | Helm version example | --devel required |
Description |
|---|---|---|---|
| Stable | 2026.1.14 |
No | Production-ready releases |
| LTS | 2025.3.18 |
No | Long-term support releases |
| Beta | 2026.1.3-beta |
Yes | Pre-release versions for early testing |
By default, helm install and helm search only show Stable and LTS versions. To include Beta releases, add the --devel flag:
# Search for all versions including beta
helm search repo devolutions/devolutions-server --devel
# Install a specific beta version
helm install dvls devolutions/devolutions-server --version 2026.1.3-betaThe chart expects the following Kubernetes secrets to exist before installation. Create them using your preferred method (Terraform, Vault, sealed-secrets, etc.). The kubectl examples below are for illustration only.
Docker Hub registry credentials (referenced by imagePullSecrets):
kubectl create secret docker-registry docker-hub \
--docker-server=https://index.docker.io/v1/ \
--docker-username='<username>' \
--docker-password='<password>'Docker Hardened Image (DHI) registry credentials — required when migration.enabled is true (the default), because the scale-down hook pulls dhi.io/kubectl:1.35-compat. Skip this if you disable migrations or override migration.kubectl.image to a registry you can already pull from.
kubectl create secret docker-registry dhi-io \
--docker-server=dhi.io \
--docker-username='<dhi-username>' \
--docker-password='<dhi-password>'Then list both pull secrets in your values file:
imagePullSecrets:
- name: docker-hub
- name: dhi-ioDVLS credentials (referenced by existingSecret):
kubectl create secret generic devolutions-server \
--from-literal=dvls-admin-password='<admin-password>' \
--from-literal=dvls-encryption-config='<base64-encoded-encryption-config>' \
--from-literal=db-username='<database-username>' \
--from-literal=db-password='<database-password>'The secret must contain the following keys:
| Key | Description |
|---|---|
dvls-admin-password |
Admin account password |
dvls-encryption-config |
Base64-encoded encryption configuration |
db-username |
Database username |
db-password |
Database password |
Note: The database key names
db-usernameanddb-passwordare defaults. You can use any key names by settingdatabase.usernameSecretKeyanddatabase.passwordSecretKey. The DVLS container supports bothDATABASE_*(default) andAZURE_SQL_*environment variable prefixes — setdatabase.envPrefixto switch.
To obtain the encryption configuration, follow the Devolutions Server first-time setup guide. You can select your OS at the top of the documentation page.
Create a values file for your environment (e.g. values-production.yaml):
replicaCount: 1
# Overrides the image tag whose default is the chart appVersion
image:
tag: '2026.1.14.0'
imagePullSecrets:
- name: docker-hub
- name: dhi-io
dvls:
hostname: dvls.example.com
admin:
email: admin@example.com
database:
host: sqlserver.example.com
name: dvls-db
aspnetcore:
environment: Production
certificate:
issuerName: letsencrypt
secretName: cert-dvls-example-com
# Optional: Gateway API HTTPRoute
httproute:
enabled: true
gateway:
name: my-gateway
namespace: istio-system
sectionName: https-dvls
# Optional: Istio DestinationRule for TLS origination
destinationRule:
enabled: true
nodeSelector:
workload: apps
existingSecret: devolutions-serverhelm upgrade --install dvls devolutions/devolutions-server \
-f values-production.yaml \
-n devolutions-server --create-namespace \
--wait --timeout 15mBefore applying changes, you can preview what will be modified using the helm-diff plugin (requires Helm 3.17+ or 4.x):
helm diff upgrade dvls devolutions/devolutions-server \
-f values-production.yaml \
-n devolutions-serverThe chart includes pre-upgrade migration hooks. When migration.enabled=true (the default), the upgrade process:
- RBAC setup (hook weight -15): Creates ServiceAccount, Role, and RoleBinding for migration jobs
- Scale down (hook weight -10): Scales deployment to 0 replicas and waits for pods to terminate
- Migration (hook weight -5): Runs database migration with
DVLS_UPDATE_MODE=true - Deploy: Updates deployment with new image version
To upgrade, update the image.tag in your values file and run:
helm upgrade dvls devolutions/devolutions-server \
-f values-production.yaml \
-n devolutions-server \
--wait --timeout 15mUse --wait so Helm only returns when the Deployment is ready (based on the readiness probe).
For hotfixes that don't require database changes:
helm upgrade dvls devolutions/devolutions-server \
-f values-production.yaml \
--set migration.enabled=false \
-n devolutions-server \
--wait --timeout 15mhelm rollback dvls -n devolutions-serverNote: If the upgrade included a database migration, you must restore the database from a snapshot before rolling back the image. See Migration job failing for details.
| Parameter | Description | Default |
|---|---|---|
image.repository |
Container image repository | devolutions/devolutions-server |
image.tag |
Image tag (defaults to appVersion) |
"" |
image.pullPolicy |
Image pull policy | IfNotPresent |
imagePullSecrets |
Image pull secrets | [] |
| Parameter | Description | Default |
|---|---|---|
nameOverride |
Override chart name | "" |
fullnameOverride |
Override release fullname | "" |
selectorLabels |
Override selector labels (for migration from existing releases) | {} |
| Parameter | Description | Default |
|---|---|---|
dvls.hostname |
External hostname for DVLS (required) | "" |
dvls.admin.username |
Admin username | dvls-admin |
dvls.admin.email |
Admin email address | "" |
dvls.admin.passwordSecretKey |
Key in existingSecret for admin password |
dvls-admin-password |
dvls.path |
DVLS path inside container | /opt/devolutions/dvls |
dvls.telemetry |
Enable telemetry | false |
dvls.encryptionConfigSecretKey |
Key in existingSecret for encryption config |
dvls-encryption-config |
| Parameter | Description | Default |
|---|---|---|
database.host |
SQL Server hostname (required) | "" |
database.name |
Database name (required) | "" |
database.port |
Database port | 1433 |
database.envPrefix |
Environment variable prefix (DATABASE or AZURE_SQL) |
DATABASE |
database.usernameSecretKey |
Key in existingSecret for DB username |
db-username |
database.passwordSecretKey |
Key in existingSecret for DB password |
db-password |
| Parameter | Description | Default |
|---|---|---|
certificate.enabled |
Create a cert-manager Certificate resource | true |
certificate.name |
Certificate resource name | <release>-tls |
certificate.secretName |
TLS secret name | <release>-tls |
certificate.issuerName |
cert-manager ClusterIssuer name (required when certificate enabled) | "" |
certificate.issuerKind |
Issuer kind | ClusterIssuer |
certificate.privateKeyRotationPolicy |
Private key rotation policy | Always |
| Parameter | Description | Default |
|---|---|---|
service.type |
Service type | ClusterIP |
service.port |
Service port | 5000 |
service.targetPort |
Container target port | 5000 |
httproute.enabled |
Create a Gateway API HTTPRoute | false |
httproute.gateway.name |
Gateway name | "" |
httproute.gateway.namespace |
Gateway namespace | "" |
httproute.gateway.sectionName |
Gateway section name | "" |
backendTLSPolicy.enabled |
Create a Gateway API BackendTLSPolicy | false |
backendTLSPolicy.wellKnownCACertificates |
Use well-known CAs (e.g., System) |
"" |
backendTLSPolicy.caCertificateRefs |
CA certificate refs for backend TLS validation | [] |
destinationRule.enabled |
Create an Istio DestinationRule | false |
| Parameter | Description | Default |
|---|---|---|
replicaCount |
Number of replicas | 1 |
resources.requests.memory |
Memory request | 512Mi |
resources.limits.memory |
Memory limit | 1Gi |
nodeSelector |
Node selector labels | {} |
affinity |
Affinity rules | {} |
tolerations |
Tolerations | [] |
topologySpreadConstraints |
Topology spread constraints | [] |
strategy.type |
Deployment strategy | Recreate |
| Parameter | Description | Default |
|---|---|---|
migration.enabled |
Enable pre-upgrade migration hook | true |
migration.image.repository |
Migration image (defaults to main image) | "" |
migration.image.tag |
Migration image tag (defaults to main tag) | "" |
migration.kubectl.image |
kubectl image for scale-down job (DHI requires imagePullSecrets for dhi.io) |
dhi.io/kubectl |
migration.kubectl.tag |
kubectl image tag — -compat variant is required because the hook runs /bin/sh; pin to your cluster's Kubernetes minor (current or n-1) |
1.35-compat |
migration.activeDeadlineSeconds |
Migration job deadline (seconds) | 600 |
migration.backoffLimit |
Job backoff limit | 0 |
migration.ttlSecondsAfterFinished |
Job TTL after completion | 604800 |
migration.backupPath |
Backup mount path | /backup |
migration.backupVolumeSizeLimit |
Backup volume size | 2Gi |
| Parameter | Description | Default |
|---|---|---|
existingSecret |
Name of the Kubernetes secret | devolutions-server |
podSecurityContext.fsGroup |
Pod filesystem group | 1000 |
securityContext.runAsNonRoot |
Run as non-root | true |
securityContext.runAsUser |
Run as user ID | 1000 |
securityContext.runAsGroup |
Run as group ID | 1000 |
securityContext.allowPrivilegeEscalation |
Allow privilege escalation | false |
securityContext.capabilities.drop |
Linux capabilities to drop | ["ALL"] |
securityContext.seccompProfile.type |
Seccomp profile type | RuntimeDefault |
Devolutions Server container images are signed with Notation using the Devolutions EV code signing certificate issued by GlobalSign. Signatures are COSE, attached as OCI 1.1 referrers (no separate .sig tag), and RFC 3161 timestamped against the GlobalSign TSA.
Install the notation CLI, then add the GlobalSign code signing chain to a trust store:
cat > globalsign-codesign-chain.pem <<'EOF'
-----BEGIN CERTIFICATE-----
MIIG6DCCBNCgAwIBAgIQd70OBbdZC7YdR2FTHj917TANBgkqhkiG9w0BAQsFADBT
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEpMCcGA1UE
AxMgR2xvYmFsU2lnbiBDb2RlIFNpZ25pbmcgUm9vdCBSNDUwHhcNMjAwNzI4MDAw
MDAwWhcNMzAwNzI4MDAwMDAwWjBcMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xv
YmFsU2lnbiBudi1zYTEyMDAGA1UEAxMpR2xvYmFsU2lnbiBHQ0MgUjQ1IEVWIENv
ZGVTaWduaW5nIENBIDIwMjAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQDLIO+XHrkBMkOgW6mKI/0gXq44EovKLNT/QdgaVdQZU7f9oxfnejlcwPfOEaP5
pe0B+rW6k++vk9z44rMZTIOwSkRQBHiEEGqk1paQjoH4fKsvtaNXM9JYe5QObQ+l
kSYqs4NPcrGKe2SS0PC0VV+WCxHlmrUsshHPJRt9USuYH0mjX/gTnjW4AwLapBMv
hUrvxC9wDsHUzDMS7L1AldMRyubNswWcyFPrUtd4TFEBkoLeE/MHjnS6hICf0qQV
Duiv6/eJ9t9x8NG+p7JBMyB1zLHV7R0HGcTrJnfyq20Xk0mpt+bDkJzGuOzMyXua
XsXFJJNjb34Qi2HPmFWjJKKINvL5n76TLrIGnybADAFWEuGyip8OHtyYiy7P2uKJ
NKYfJqCornht7KGIFTzC6u632K1hpa9wNqJ5jtwNc8Dx5CyrlOxYBjk2SNY7Wugi
znQOryzxFdrRtJXorNVJbeWv3ZtrYyBdjn47skPYYjqU5c20mLM3GSQScnOrBLAJ
3IXm1CIE70AqHS5tx2nTbrcBbA3gl6cW5iaLiPcDRIZfYmdMtac3qFXcAzaMbs9t
NibxDo+wPXHA4TKnguS2MgIyMHy1k8gh/TyI5mlj+O51yYvCq++6Ov3pXr+2EfG+
8D3KMj5ufd4PfpuVxBKH5xq4Tu4swd+hZegkg8kqwv25UwIDAQABo4IBrTCCAakw
DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBIGA1UdEwEB/wQI
MAYBAf8CAQAwHQYDVR0OBBYEFCWd0PxZCYZjxezzsRM7VxwDkjYRMB8GA1UdIwQY
MBaAFB8Av0aACvx4ObeltEPZVlC7zpY7MIGTBggrBgEFBQcBAQSBhjCBgzA5Bggr
BgEFBQcwAYYtaHR0cDovL29jc3AuZ2xvYmFsc2lnbi5jb20vY29kZXNpZ25pbmdy
b290cjQ1MEYGCCsGAQUFBzAChjpodHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29t
L2NhY2VydC9jb2Rlc2lnbmluZ3Jvb3RyNDUuY3J0MEEGA1UdHwQ6MDgwNqA0oDKG
MGh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY29kZXNpZ25pbmdyb290cjQ1LmNy
bDBVBgNVHSAETjBMMEEGCSsGAQQBoDIBAjA0MDIGCCsGAQUFBwIBFiZodHRwczov
L3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAHBgVngQwBAzANBgkqhkiG
9w0BAQsFAAOCAgEAJXWgCck5urehOYkvGJ+r1usdS+iUfA0HaJscne9xthdqawJP
sz+GRYfMZZtM41gGAiJm1WECxWOP1KLxtl4lC3eW6c1xQDOIKezu86JtvE21PgZL
yXMzyggULT1M6LC6daZ0LaRYOmwTSfilFQoUloWxamg0JUKvllb0EPokffErcsEW
4Wvr5qmYxz5a9NAYnf10l4Z3Rio9I30oc4qu7ysbmr9sU6cUnjyHccBejsj70yqS
M+pXTV4HXsrBGKyBLRoh+m7Pl2F733F6Ospj99UwRDcy/rtDhdy6/KbKMxkrd23b
ywXwfl91LqK2vzWqNmPJzmTZvfy8LPNJVgDIEivGJ7s3r1fvxM8eKcT04i3OKmHP
V+31CkDi9RjWHumQL8rTh1+TikgaER3lN4WfLmZiml6BTpWsVVdD3FOLJX48YQ+K
C7r1P6bXjvcEVl4hu5/XanGAv5becgPY2CIr8ycWTzjoUUAMrpLvvj1994DGTDZX
hJWnhBVIMA5SJwiNjqK9IscZyabKDqh6NttqumFfESSVpOKOaO4ZqUmZXtC0NL3W
+UDHEJcxUjk1KRGHJNPE+6ljy3dI1fpi/CTgBHpO0ORu3s6eOFAm9CFxZdcJJdTJ
BwB6uMfzd+jF1OJV0NMe9n9S4kmNuRFyDIhEJjNmAUTf5DMOId5iiUgH2vU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIQdlP+rHVGSJP15ddKSDpO+DANBgkqhkiG9w0BAQwFADBT
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEpMCcGA1UE
AxMgR2xvYmFsU2lnbiBDb2RlIFNpZ25pbmcgUm9vdCBSNDUwHhcNMjAwMzE4MDAw
MDAwWhcNNDUwMzE4MDAwMDAwWjBTMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xv
YmFsU2lnbiBudi1zYTEpMCcGA1UEAxMgR2xvYmFsU2lnbiBDb2RlIFNpZ25pbmcg
Um9vdCBSNDUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2LcUw3Xro
q5A9A3KwOkuZFmGy5f+lZx03HOV+7JODqoT1o0ObmEWKuGNXXZsAiAQl6fhokkuC
2EvJSgPzqH9qj4phJ72hRND99T8iwqNPkY2zBbIogpFd+1mIBQuXBsKY+CynMyTu
UDpBzPCgsHsdTdKoWDiW6d/5G5G7ixAs0sdDHaIJdKGAr3vmMwoMWWuOvPSrWpd7
f65V+4TwgP6ETNfiur3EdaFvvWEQdESymAfidKv/aNxsJj7pH+XgBIetMNMMjQN8
VbgWcFwkeCAl62dniKu6TjSYa3AR3jjK1L6hwJzh3x4CAdg74WdDhLbP/HS3L4Sj
v7oJNz1nbLFFXBlhq0GD9awd63cNRkdzzr+9lZXtnSuIEP76WOinV+Gzz6ha6Qcl
mxLEnoByPZPcjJTfO0TmJoD80sMD8IwM0kXWLuePmJ7mBO5Cbmd+QhZxYucE+WDG
ZKG2nIEhTivGbWiUhsaZdHNnMXqR8tSMeW58prt+Rm9NxYUSK8+aIkQIqIU3zgdh
VwYXEiTAxDFzoZg1V0d+EDpF2S2kUZCYqaAHN8RlGqocaxZ396eX7D8ZMJlvMfvq
QLLn0sT6ydDwUHZ0WfqNbRcyvvjpfgP054d1mtRKkSyFAxMCK0KA8olqNs/ITKDO
nvjLja0Wp9Pe1ZsYp8aSOvGCY/EuDiRk3wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC
AYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUHwC/RoAK/Hg5t6W0Q9lWULvO
ljswDQYJKoZIhvcNAQEMBQADggIBAF4runSXNERfdkgoQIST7gFu6aGz1oAl5nvk
vAmRPQ/8dq3X1DAgu49g0JHWHPKc73gaK5QyAsEkllJSAtDz0fzymzlumeEfjkNB
fZoeW8ldmoT8JuaH83RyJq2kG9k9O2pSoDwJHi8ee7MztEXH96yxr5NgrXauuLIV
eOuDauv/20arJOXuAvqQH1nAL13Wt12kXBC3clP4QU7M+ngaJUrK/oViQ2HDtDeq
gdL01joPvY1ZfjBH3itr5yFQM1/UZ5vUuGefPCeZA/+FQ45zEsogzehh1bFm3BfW
OW0P288jN6GCiU4caz/WoM2qB50+Qiaq1wzu+ke/GlJ+0XWB08mKYhdtT4igIaAm
Pq9t2WIwH+mYKK5ujdWOTHJmk4CNKuNVx2BnkEJWXCJRD7PcTjnuTd3ZHXgQVDtu
0JdvA7UesiNzxhKymmTQ/JWFJKj/36Gw3JFArt8JM6u53ZK38cyRdDtp62eXG5C/
58egb3G7V7+3j1rtekBqFs2AhC0v4QLUJJRDsxX8DCsb/XFv/Mu8dRc6XoPSybMv
G9WcjX9U/n5+5Fajh6ed4VlSlEGPbVu+hpWa/xp23UDSUUpwtB8zYyN3P+wnHlnk
CIftNIJKDz/+oB3B9WdzRYZ49Kop6SeHxhnbxhMUwzlJh02gl+BlE/Wdd1bp2rNY
xzrywM2C
-----END CERTIFICATE-----
EOF
notation cert add --type ca --store devolutions globalsign-codesign-chain.pemThe chain alone would trust every GlobalSign EV code signing certificate, so pin trustedIdentities to the Devolutions subject:
cat > trustpolicy.json <<'EOF'
{
"version": "1.0",
"trustPolicies": [
{
"name": "devolutions-server",
"registryScopes": [ "docker.io/devolutions/devolutions-server" ],
"signatureVerification": { "level": "strict" },
"trustStores": [ "ca:devolutions", "tsa:globalsign" ],
"trustedIdentities": [ "x509.subject: C=CA, ST=Quebec, L=Lavaltrie, O=DEVOLUTIONS INC., CN=DEVOLUTIONS INC." ]
}
]
}
EOF
notation policy import trustpolicy.jsonSignatures are timestamped, so the timestamping authority needs its own trust store. Without it the signature stops verifying once the signing certificate expires:
cat > globalsign-root-r6.pem <<'EOF'
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----
EOF
notation cert add --type tsa --store globalsign globalsign-root-r6.pemVerify by digest rather than by tag, since a tag can be moved after you read it:
DIGEST=$(docker buildx imagetools inspect devolutions/devolutions-server:<tag> --format '{{json .Manifest.Digest}}' | tr -d '"')
notation verify devolutions/devolutions-server@$DIGESTIf you use Kyverno, you can enforce image signature verification at the cluster level with an ImageValidatingPolicy. The example below audits pods in a specific namespace — change validationActions to ["Deny"] to block unsigned images.
The notary attestor does not expose trustedIdentities, so trust is pinned to the Devolutions signing certificates themselves rather than to the CA chain. Two certificates are listed so signatures stay verifiable across a certificate rotation. tsaCerts holds the root of the timestamping authority.
apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-dvls-image-signatures
spec:
webhookConfiguration:
timeoutSeconds: 15
evaluation:
background:
enabled: true
validationActions: ["Audit"]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: devolutions-server # adjust to your namespace
matchImageReferences:
- glob: "devolutions/devolutions-server:*"
- glob: "devolutions/devolutions-server@*"
credentials:
secrets: ["docker-hub"] # your Docker Hub pull secret, in the Kyverno namespace
attestors:
- name: notary
notary:
certs:
value: |
-----BEGIN CERTIFICATE-----
MIIHsTCCBZmgAwIBAgIMc9PDNgP/i7RCJPJeMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNVBAYTAkJF
MRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEdDQyBSNDUg
RVYgQ29kZVNpZ25pbmcgQ0EgMjAyMDAeFw0yMzEwMzAxNzUxMThaFw0yNjEwMzAxNzUxMThaMIHx
MR0wGwYDVQQPDBRQcml2YXRlIE9yZ2FuaXphdGlvbjETMBEGA1UEBRMKMTE2MjU0NDY4OTETMBEG
CysGAQQBgjc8AgEDEwJDQTEXMBUGCysGAQQBgjc8AgECEwZRdWViZWMxCzAJBgNVBAYTAkNBMQ8w
DQYDVQQIEwZRdWViZWMxEjAQBgNVBAcTCUxhdmFsdHJpZTEYMBYGA1UEChMPRGV2b2x1dGlvbnMg
SW5jMRgwFgYDVQQDEw9EZXZvbHV0aW9ucyBJbmMxJzAlBgkqhkiG9w0BCQEWGHNlY3VyaXR5QGRl
dm9sdXRpb25zLm5ldDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJ8OTpzV4Iv2tO+r
UPWWrUaZTTxkrJhAlDsRv+ZEWlFeqk4WLJKd/wHKxhtnjLgyciXszZaNzmfUlxdH0E9aaQkucjus
VPCmr87nEpTBbbT8RjI64XtNqxGrqiWWObvd1wuOu3nP9ra7aA768xLwtVjpRcoAZkYiKAyg9L3Z
/YySQqZ0SYDl2nBsAtR+8f2zLSqSdR9Bjp2yWkjw9uNMLH0ZjnGoJMy0FBxYHmwGf8jRgCWnnK46
f7aBri9Ry5wBlNWx6hEj8myfkpZZvSIz3Ctu/4M4LNwC0EX5iPYqnzAdFZ8wK6a7hi5hzBNjeFsi
41GhSLyPicum2MZrPtHdR8Cvhv+sfhWDz+X258/rVntulKRlsiWeHcaPL1QkKPDnCC5C5yeWVJs0
2DlkF3u/cNFQrAq/MX1Nig4RHAZ15jy5Lh+dJg/te4YX1v5yhn8PmC4Zp5uIkkSh1EmQZ2I/k/7q
Ms7jd3OCYHiGZZu4XnCh9Fhd3WKEU5/hoEfarMecWQO+nnN5yUyWCgu7ElVviZTfpnzgqcm5Pt89
OEr1Fs0Sio8/N3UFhwJxGZVosJgfD7oCCZVebduAKy/jMz8OqTJx89fXWwFd51h1Mni2KG0WjV5G
p9CxcSK835djBQgn8R18dSZodT7t5iGBI9XKc+b0WrWYAALcqof7pG0ikSalAgMBAAGjggHbMIIB
1zAOBgNVHQ8BAf8EBAMCB4AwgZ8GCCsGAQUFBwEBBIGSMIGPMEwGCCsGAQUFBzAChkBodHRwOi8v
c2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2djY3I0NWV2Y29kZXNpZ25jYTIwMjAuY3J0
MD8GCCsGAQUFBzABhjNodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9nc2djY3I0NWV2Y29kZXNp
Z25jYTIwMjAwVQYDVR0gBE4wTDBBBgkrBgEEAaAyAQIwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93
d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wBwYFZ4EMAQMwCQYDVR0TBAIwADBHBgNVHR8E
QDA+MDygOqA4hjZodHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1ZXZjb2Rlc2lnbmNh
MjAyMC5jcmwwIwYDVR0RBBwwGoEYc2VjdXJpdHlAZGV2b2x1dGlvbnMubmV0MBMGA1UdJQQMMAoG
CCsGAQUFBwMDMB8GA1UdIwQYMBaAFCWd0PxZCYZjxezzsRM7VxwDkjYRMB0GA1UdDgQWBBT5ymf4
g+pZGcTmsd4j5s7xv9fz2jANBgkqhkiG9w0BAQsFAAOCAgEAGvu6RRlge5FgpvQl2hWH0vCeCfjm
b8EGD3SNMvkIXpk/jFgHaRoo3frqx8Bu+YpOFuB9wi83bo2NLX9wVdp3lp/qzk7MZSJz6YAVk6Fu
lfzUZ52wCfGXUPgEomzb6JaH94ra9tr8rcnlXZntLtgWAeoXS+WYO03GcFDyOwjfTOtty5gmjB+3
xYuN9biGvRJ0AiTYXhfUJMaG0lUy49zHJS6+uaSenWDbL32Nzl5cDqqnQKJRsULVHcLWSllhPizG
K7zoHeRtjompM7Z/Ty2O+mKHfpR4UIL8HJkHNvUPwUUhoqISuOUMdUwgEJjVesQQMQmkjIxHKhte
wi6KzKfhOkrwmpFvQLSikPO8TwGUq+qWqYd9p9s5RcUfmDP8X1qIkAx8fKHh11SD2cVwX5gpYqny
Gl1ohb7mm2WwLYtJLm3O0xRdGKxR//MJN4tDYwBdztWXSxzxkP1Spv3Cb62Yrdka+cMoKKTATxhT
7L3qiAVTJsZwRZdRiTdC+5cp5LT3+/pA7BuBjejkSSs1DI9S6AjYezJa2YuFN0Mz8+eP47Y0M2Q9
e8aKyzZvQ7zJQxWSH0LQcbhZLcv8TGgzWR43Vh3ngVoWGTXNC/cpBoLswlTy5muasgks810Q8YqI
V8jwIshX/TiMAitH4DhINoKkNPP5cNkXuQ/jYobJsmJIPWo=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHszCCBZugAwIBAgIMTBZCMes1fh+PwkUoMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNVBAYTAkJF
MRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEdDQyBSNDUg
RVYgQ29kZVNpZ25pbmcgQ0EgMjAyMDAeFw0yNTEyMDQyMTAyMjRaFw0yODEyMDQyMTAyMjRaMIHz
MR0wGwYDVQQPDBRQcml2YXRlIE9yZ2FuaXphdGlvbjETMBEGA1UEBRMKMTE2MjU0NDY4OTETMBEG
CysGAQQBgjc8AgEDEwJDQTEXMBUGCysGAQQBgjc8AgECEwZRdWViZWMxCzAJBgNVBAYTAkNBMQ8w
DQYDVQQIEwZRdWViZWMxEjAQBgNVBAcTCUxhdmFsdHJpZTEZMBcGA1UEChMQREVWT0xVVElPTlMg
SU5DLjEZMBcGA1UEAxMQREVWT0xVVElPTlMgSU5DLjEnMCUGCSqGSIb3DQEJARYYc2VjdXJpdHlA
ZGV2b2x1dGlvbnMubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq8ZKamwIPGJX
h9qmwU8JWE0yl92WCvMWEKJedfQbLtm7Z7twMyXZE2jQfyGko0eFI4OSC5Zg0tl+Yz5um8TYVss3
ShjFSaUQeIFlY3z7R2rXTJuR9Bmn4773lLeZfd5x0pMGE9gHsjkdg1YE9HUC7zpjrzH4sFpO0zcB
j/O/AHaAxt59mwoBr0v2enWXtSM3qp9myRAb7NHEKP7zwKDqyuQJjI278sycW1YidtvG6qSHV11N
MCZVVJQ5def68q1QTaFPD/bnHemxscsuBwturQThnDOZE8Hamz3vZqi4RoyQ+Fhc7gpO1NkWPoah
jLawBRobGA+AAMR5lj+C6GpIEA2efZZrqNqhyAy+82FIl3tpB+V2+mzRlDJ6tWtrvOCipPQmTIs0
eb0AqI1X6YlAeqKtfEv5jvS87i4UlW6TK9x4074L7LRF7vulvwGe1F+5I51zJKPW/81Dt4cBXr/N
jbXGVizAjPu7KygdB5kQxaURfxuMk2MO+RPUFQX2PrnmFV7LGlnO9pKAt2l2llVI5DNKR2IfQo6E
GJD1Hk+lrb9b2qF0s2L6wqOvX/EQpI53V8UbnpGI3h6az1h0RBH0qucatWlBDRApK62oWJZSVSoK
Ba4PBM97ZX5S8drbECVmVtgDMZ9fxzo4ieIUuNZzFcpXbVHtU/xR9DGt/fw9qkkCAwEAAaOCAdsw
ggHXMA4GA1UdDwEB/wQEAwIHgDCBnwYIKwYBBQUHAQEEgZIwgY8wTAYIKwYBBQUHMAKGQGh0dHA6
Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzZ2NjcjQ1ZXZjb2Rlc2lnbmNhMjAyMC5j
cnQwPwYIKwYBBQUHMAGGM2h0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1ZXZjb2Rl
c2lnbmNhMjAyMDBVBgNVHSAETjBMMEEGCSsGAQQBoDIBAjA0MDIGCCsGAQUFBwIBFiZodHRwczov
L3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAHBgVngQwBAzAJBgNVHRMEAjAAMEcGA1Ud
HwRAMD4wPKA6oDiGNmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vZ3NnY2NyNDVldmNvZGVzaWdu
Y2EyMDIwLmNybDAjBgNVHREEHDAagRhzZWN1cml0eUBkZXZvbHV0aW9ucy5uZXQwEwYDVR0lBAww
CgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAUJZ3Q/FkJhmPF7POxEztXHAOSNhEwHQYDVR0OBBYEFBz0
BXTVeq8fOSR5EOPQV6iQY+fAMA0GCSqGSIb3DQEBCwUAA4ICAQC67LrgjleBhagrOdc4rMExTPg1
Mae/Vw3yi9f2kmVVR94iGKl98TpZkf3B4dn7c6F5OgHi/wlYShs4F9lvP/wUjbRaJeC0++pThP+O
7xmdVS/5eJ2VN1NGfoDNePKSnIlotcwkDgEy+XkhLFRViCdJw6k/qECpaw8PKnkJU4uZhbNk0pa9
8EYyIQjAL6Ez6aSooGcO7VXS1T1ANupeAGrAGnUErFaDlvExgI2QLXlbCo/xVVdGT/fnjHVRrzss
cY5IcEihufJJFsr6iecYRKop5ULOovkO9NUEZnKNEBFxhTSZjnWtoGcnn9hp3fLtH55Ii5SOHfkk
1fBgAdiVZdixXN9ofg6AVJpKQlwvSJ4fgxPbFPyQhM5v70oSW9xjIWXFaGAg9b++9jq+pTloyUwJ
NwhZNkFvOn13AI2wBiGGrg6SfknV7mP5tohPnw2A6GxmOwwTpU7WxiPXNSoomcPNry9WJk7uQpod
e2DrDYKC9880o761mdxYRfdggRp2m3+3RQWUnmYBa2UjPF/7v0D9rwvslbFzA1xnXNS/L+aP5v/Q
bVLes8K2yHsFpSywxVwdHO5qfP7MwwFxBx3DHDIzVTl+fIWMExu+wCFEkHpzrybUhsHysz1A2uNl
fhjphPHPVQ4IyTYO6wj5AdgiWJ3MmXXq1gPcOUXnF5/HJBx0Sw==
-----END CERTIFICATE-----
tsaCerts:
value: |
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----
validationConfigurations:
required: false
verifyDigest: false
mutateDigest: false
validations:
- expression: >-
images.containers
.filter(image, image.matches("(docker\\.io/)?devolutions/devolutions-server[:@].*"))
.map(image, verifyImageSignatures(image, [attestors.notary]))
.all(e, e > 0)
message: "failed image signature verification"Tags released before Devolutions Server moved to Notation carry a cosign signature instead, and no Notation signature. Verify those with cosign and the public key below:
cat > cosign.pub <<'EOF'
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuyxOC+3ZeSW8eUalDsYQJ7411UJJ
pvL+FPxCQzgP7XnvX8nuSqN9kgd2qhBOB547Dc75eIkZC0KPm3PRvmkbGQ==
-----END PUBLIC KEY-----
EOF
cosign verify --key cosign.pub devolutions/devolutions-server:<tag>Check the pod logs and events (adjust the app label and the namespace if you use overrides):
kubectl logs -l app=devolutions-server -n devolutions-server
kubectl describe pod -l app=devolutions-server -n devolutions-serverNot all upgrades trigger a database migration — they mostly occur on new major releases. When one does run, check the migration pod logs first:
kubectl logs -l component=db-migration -n devolutions-serverFailed migration jobs are kept for debugging and cleaned up on the next upgrade or by the TTL controller.
If you need to revert after a failed migration, restore the database from a snapshot taken before the upgrade first, then roll back to the previous image version. Rolling back the image without restoring the database will leave the schema in an inconsistent state.
Verify the certificate status:
kubectl get certificate
kubectl describe certificate <release>-tls