{"record":{"id":"424c73002f721381","repo":"k3s-io/k3s","slug":"etcd-datastore-disabled","errorCode":null,"errorMessage":"etcd datastore disabled","messagePattern":"etcd datastore disabled","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"pkg/cluster/managed.go","lineNumber":151,"sourceCode":"// deleteNodePasswdSecret wipes out the node password secret after restoration\nfunc (c *Cluster) deleteNodePasswdSecret(ctx context.Context) {\n\tnodeName := os.Getenv(\"NODE_NAME\")\n\tif err := nodepassword.Delete(nodeName); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\tlogrus.Debugf(\"Node password secret is not found for node %s\", nodeName)\n\t\t\treturn\n\t\t}\n\t\tlogrus.Warnf(\"failed to delete old node password secret: %v\", err)\n\t}\n}\n\n// handlerNoEtcd wraps a handler with an error message indicating that etcd is not deployed.\nfunc handlerNoEtcd(handler http.Handler) http.Handler {\n\tr := mux.NewRouter()\n\n\t// Wildcard route for anything after /db/\n\tr.HandleFunc(\"/db/\", func(resp http.ResponseWriter, r *http.Request) {\n\t\tutil.SendError(errors.New(\"etcd datastore disabled\"), resp, r, http.StatusBadRequest)\n\t})\n\n\t// Needs to come at the end, otherwise wildcard routes won't work\n\tr.NotFoundHandler = handler\n\n\treturn r\n}\n","sourceCodeStart":133,"sourceCodeEnd":159,"githubUrl":"https://github.com/k3s-io/k3s/blob/6ba341e396edc16b8dcae978a7c5e3ac7ee5606e/pkg/cluster/managed.go#L133-L159","documentation":"When the server runs a non-etcd datastore (sqlite/postgres/mysql via --datastore-endpoint, or etcd disabled), the /db/ mux is wrapped by handlerNoEtcd: every request under /db/ answers 400 with 'etcd datastore disabled'. The etcd-specific database API simply does not exist for this datastore backend.","triggerScenarios":"Calling etcd-only endpoints (/db/info, /db/snapshot, /db/check, /db/connect) on a server started with a SQL datastore or with the managed etcd disabled; an agent or script hardcoding the etcd join flow against such a server.","commonSituations":"Migrating a cluster to sqlite/PostgreSQL and reusing old automation that hits /db routes; mixed servers where a joining node assumes embedded etcd; monitoring scraping etcd metrics endpoints.","solutions":["Use datastore-appropriate tooling: SQL snapshots are handled by the local/k3s etcd-snapshot commands or database-native backups, not /db/snapshot.","Join additional servers by pointing them at the same --datastore-endpoint, not via the etcd /db join path.","Update scripts/agents to detect datastore type before calling /db endpoints and skip on non-etcd clusters."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Only call /db endpoints when the datastore is etcd:\nfunc isEtcdDatastore(cfg Endpoint) bool {\n\treturn strings.HasPrefix(cfg.DatastoreEndpoint, \"etcd\") || cfg.DatastoreEndpoint == \"\" && !cfg.DisableEmbeddedEtcd\n}\nif isEtcdDatastore(cfg) { callDBInfo() } else { useSQLBackupTooling() }","typeGuard":null,"tryCatchPattern":"resp, err := http.Get(base + \"/db/snapshot\")\nif err == nil && resp.StatusCode == http.StatusBadRequest {\n\tbody, _ := io.ReadAll(resp.Body)\n\tif strings.Contains(string(body), \"etcd datastore disabled\") {\n\t\t// switch to datastore-native backup path instead of /db API\n\t}\n}","preventionTips":["Make datastore type a first-class variable in your automation and branch on it before /db calls.","Document which endpoints are etcd-only when adopting sqlite/PostgreSQL backends.","Join extra servers via the shared --datastore-endpoint, never via the etcd /db flow."],"tags":["k3s","etcd","datastore","http","sqlite"],"backgroundTag":null,"analyzedSha":"6ba341e396edc16b8dcae978a7c5e3ac7ee5606e","analyzedAt":"2026-08-15T16:27:54.286Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}