{"record":{"id":"b114aeb1e1d4ef01","repo":"juanfont/headscale","slug":"creating-directory-for-sqlite-w","errorCode":null,"errorMessage":"creating directory for sqlite: %w","messagePattern":"creating directory for sqlite: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/db.go","lineNumber":1069,"sourceCode":"\treturn &db, err\n}\n\nfunc openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {\n\t// TODO(kradalby): Integrate this with zerolog\n\tvar dbLogger logger.Interface\n\tif cfg.Debug {\n\t\tdbLogger = util.NewDBLogWrapper(&log.Logger, cfg.Gorm.SlowThreshold, cfg.Gorm.SkipErrRecordNotFound, cfg.Gorm.ParameterizedQueries)\n\t} else {\n\t\tdbLogger = logger.Default.LogMode(logger.Silent)\n\t}\n\n\tswitch cfg.Type {\n\tcase types.DatabaseSqlite:\n\t\tdir := filepath.Dir(cfg.Sqlite.Path)\n\n\t\terr := util.EnsureDir(dir)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"creating directory for sqlite: %w\", err)\n\t\t}\n\n\t\tlog.Info().\n\t\t\tStr(\"database\", types.DatabaseSqlite).\n\t\t\tStr(\"path\", cfg.Sqlite.Path).\n\t\t\tMsg(\"Opening database\")\n\n\t\t// Build SQLite configuration with pragmas set at connection time\n\t\tsqliteConfig := sqliteconfig.Default(cfg.Sqlite.Path)\n\t\tif cfg.Sqlite.WriteAheadLog {\n\t\t\tsqliteConfig.JournalMode = sqliteconfig.JournalModeWAL\n\t\t\tsqliteConfig.WALAutocheckpoint = cfg.Sqlite.WALAutoCheckPoint\n\t\t}\n\n\t\tconnectionURL, err := sqliteConfig.ToURL()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"building sqlite connection URL: %w\", err)\n\t\t}","sourceCodeStart":1051,"sourceCodeEnd":1087,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L1051-L1087","documentation":"Before opening a sqlite database, headscale ensures the parent directory of cfg.Database.Sqlite.Path exists (util.EnsureDir). This error means the directory could not be created: permission denied on the parent, a path component is a file, or the filesystem is read-only.","triggerScenarios":"Configuring db.path like /var/lib/headscale/headscale.db when /var/lib/headscale does not exist and the process lacks rights to mkdir it; a path whose parent component is a regular file; container mounts read-only at the wrong place.","commonSituations":"First run after installation without creating the data directory; Docker/Kubernetes volumeMount pointing at a file instead of a directory; running as a non-root user without write access to the configured parent.","solutions":["Pre-create the directory: mkdir -p /var/lib/headscale && chown headscale:headscale /var/lib/headscale.","Correct db.path in config so every parent directory is a writable directory.","In containers, verify the volume is mounted at the directory, not over a file, and is mounted rw."],"exampleFix":"// before (config.yaml)\ndb:\n  type: sqlite\n  sqlite:\n    path: /var/lib/headscale/db.sqlite\n# -> error if /var/lib/headscale is not creatable by the process user\n\n// fix\nsudo mkdir -p /var/lib/headscale\nsudo chown headscale:headscale /var/lib/headscale","handlingStrategy":"validation","validationCode":"// Deployment check: ensure the sqlite parent dir exists and is writable\n// bash -c 'mkdir -p \"$(dirname \"$DB_PATH\")\" && [ -w \"$(dirname \"$DB_PATH\")\" ]'","typeGuard":null,"tryCatchPattern":"// Configuration error — no retry. Fix the path/permissions and restart.\n// In code embedding headscale, validate cfg.Sqlite.Path before calling\n// db.NewDatabase().","preventionTips":["Package installations should ship a systemd RuntimeDirectory/StateDirectory directive.","In Kubernetes, mount a PersistentVolume at the DB directory, not over a file.","Lint config with `headscale configtest` (if available) before first run."],"tags":["database","sqlite","filesystem","permissions","config"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}