diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts
index 573fcf95375e938d08f7354fb8f86e0fda741f72..259f893928a03c3bde1af6f647208ff9b9f62f16 100644
--- a/lib/modules/manager/api.ts
+++ b/lib/modules/manager/api.ts
@@ -28,6 +28,7 @@ import * as copier from './copier';
 import * as cpanfile from './cpanfile';
 import * as crossplane from './crossplane';
 import * as depsEdn from './deps-edn';
+import * as devbox from './devbox';
 import * as devContainer from './devcontainer';
 import * as dockerCompose from './docker-compose';
 import * as dockerfile from './dockerfile';
@@ -135,6 +136,7 @@ api.set('copier', copier);
 api.set('cpanfile', cpanfile);
 api.set('crossplane', crossplane);
 api.set('deps-edn', depsEdn);
+api.set('devbox', devbox);
 api.set('devcontainer', devContainer);
 api.set('docker-compose', dockerCompose);
 api.set('dockerfile', dockerfile);
diff --git a/lib/modules/manager/devbox/artifacts.spec.ts b/lib/modules/manager/devbox/artifacts.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f2ffbd4dcfc2de64d902922fb233e4340391bae9
--- /dev/null
+++ b/lib/modules/manager/devbox/artifacts.spec.ts
@@ -0,0 +1,243 @@
+import { codeBlock } from 'common-tags';
+import { mockExecAll } from '../../../../test/exec-util';
+import { fs, git, partial } from '../../../../test/util';
+import { GlobalConfig } from '../../../config/global';
+import type { RepoGlobalConfig } from '../../../config/types';
+import type { StatusResult } from '../../../util/git/types';
+import type { UpdateArtifact } from '../types';
+import { updateArtifacts } from './artifacts';
+
+jest.mock('../../../util/exec/env');
+jest.mock('../../../util/git');
+jest.mock('../../../util/fs');
+
+const globalConfig: RepoGlobalConfig = {
+  localDir: '',
+};
+
+const devboxJson = codeBlock`
+  {
+    "$schema": "https://raw.githubusercontent.com/jetpack-io/devbox/0.10.1/.schema/devbox.schema.json",
+    "packages": ["nodejs@20", "metabase@0.49.1", "postgresql@latest", "gh@latest"],
+  }
+`;
+
+describe('modules/manager/devbox/artifacts', () => {
+  describe('updateArtifacts()', () => {
+    let updateArtifact: UpdateArtifact;
+
+    beforeEach(() => {
+      GlobalConfig.set(globalConfig);
+      updateArtifact = {
+        config: {},
+        newPackageFileContent: '',
+        packageFileName: '',
+        updatedDeps: [],
+      };
+    });
+
+    it('skips if no updatedDeps and no lockFileMaintenance', async () => {
+      expect(await updateArtifacts(updateArtifact)).toBeNull();
+    });
+
+    it('skips if no lock file in config', async () => {
+      updateArtifact.updatedDeps = [{}];
+      expect(await updateArtifacts(updateArtifact)).toBeNull();
+    });
+
+    it('skips if cannot read lock file', async () => {
+      updateArtifact.updatedDeps = [
+        { manager: 'devbox', lockFiles: ['devbox.lock'] },
+      ];
+      expect(await updateArtifacts(updateArtifact)).toBeNull();
+    });
+
+    it('returns installed devbox.lock', async () => {
+      fs.getSiblingFileName.mockReturnValueOnce('devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`);
+      const execSnapshots = mockExecAll();
+      const oldLockFileContent = Buffer.from('Old devbox.lock');
+      const newLockFileContent = Buffer.from('New devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never);
+      fs.readLocalFile.mockResolvedValueOnce(newLockFileContent as never);
+      expect(
+        await updateArtifacts({
+          packageFileName: 'devbox.json',
+          newPackageFileContent: devboxJson,
+          updatedDeps: [{ manager: 'devbox', lockFiles: ['devbox.lock'] }],
+          config: {},
+        }),
+      ).toEqual([
+        {
+          file: {
+            type: 'addition',
+            path: 'devbox.lock',
+            contents: newLockFileContent,
+          },
+        },
+      ]);
+      expect(execSnapshots).toMatchObject([
+        {
+          cmd: 'devbox install',
+          options: {
+            cwd: '.',
+            encoding: 'utf-8',
+            env: {},
+            maxBuffer: 10485760,
+            timeout: 900000,
+          },
+        },
+      ]);
+    });
+
+    it('returns updated devbox.lock', async () => {
+      fs.getSiblingFileName.mockReturnValueOnce('devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`);
+      const execSnapshots = mockExecAll();
+      git.getRepoStatus.mockResolvedValueOnce(
+        partial<StatusResult>({
+          modified: ['devbox.lock'],
+        }),
+      );
+      const oldLockFileContent = Buffer.from('old devbox.lock');
+      const newLockFileContent = Buffer.from('New devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never);
+      fs.readLocalFile.mockResolvedValueOnce(newLockFileContent as never);
+      expect(
+        await updateArtifacts({
+          packageFileName: 'devbox.json',
+          newPackageFileContent: devboxJson,
+          updatedDeps: [{}],
+          config: {
+            isLockFileMaintenance: true,
+          },
+        }),
+      ).toEqual([
+        {
+          file: {
+            type: 'addition',
+            path: 'devbox.lock',
+            contents: newLockFileContent,
+          },
+        },
+      ]);
+      expect(execSnapshots).toMatchObject([
+        {
+          cmd: 'devbox update',
+          options: {
+            cwd: '.',
+            encoding: 'utf-8',
+            env: {},
+            maxBuffer: 10485760,
+            timeout: 900000,
+          },
+        },
+      ]);
+    });
+
+    it('returns null if no changes are found', async () => {
+      fs.getSiblingFileName.mockReturnValueOnce('devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`);
+      git.getRepoStatus.mockResolvedValueOnce(
+        partial<StatusResult>({
+          modified: [],
+        }),
+      );
+      mockExecAll();
+      expect(
+        await updateArtifacts({
+          packageFileName: 'devbox.json',
+          newPackageFileContent: devboxJson,
+          updatedDeps: [],
+          config: {},
+        }),
+      ).toBeNull();
+    });
+
+    it('returns null if devbox.lock not found after update', async () => {
+      fs.getSiblingFileName.mockReturnValueOnce('devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`);
+      git.getRepoStatus.mockResolvedValueOnce(
+        partial<StatusResult>({
+          modified: [],
+        }),
+      );
+      mockExecAll();
+      const oldLockFileContent = Buffer.from('Old devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never);
+      expect(
+        await updateArtifacts({
+          packageFileName: 'devbox.json',
+          newPackageFileContent: devboxJson,
+          updatedDeps: [{}],
+          config: {},
+        }),
+      ).toBeNull();
+    });
+
+    it('returns null if devbox.lock not found', async () => {
+      fs.getSiblingFileName.mockReturnValueOnce('devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`);
+      git.getRepoStatus.mockResolvedValueOnce(
+        partial<StatusResult>({
+          modified: [],
+        }),
+      );
+      mockExecAll();
+      fs.readLocalFile.mockResolvedValueOnce(null);
+      expect(
+        await updateArtifacts({
+          packageFileName: 'devbox.json',
+          newPackageFileContent: devboxJson,
+          updatedDeps: [{}],
+          config: {},
+        }),
+      ).toBeNull();
+    });
+
+    it('returns null if no lock file changes are found', async () => {
+      fs.getSiblingFileName.mockReturnValueOnce('devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(codeBlock`{}`);
+      git.getRepoStatus.mockResolvedValueOnce(
+        partial<StatusResult>({
+          modified: [],
+        }),
+      );
+      mockExecAll();
+      const oldLockFileContent = Buffer.from('Old devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never);
+      fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never);
+      expect(
+        await updateArtifacts({
+          packageFileName: 'devbox.json',
+          newPackageFileContent: devboxJson,
+          updatedDeps: [{}],
+          config: {},
+        }),
+      ).toBeNull();
+    });
+
+    it('returns an artifact error on failure', async () => {
+      fs.getSiblingFileName.mockReturnValueOnce('devbox.lock');
+      const newLockFileContent = codeBlock`{}`;
+      const oldLockFileContent = Buffer.from('New devbox.lock');
+      fs.readLocalFile.mockResolvedValueOnce(oldLockFileContent as never);
+      fs.readLocalFile.mockResolvedValueOnce(newLockFileContent as never);
+      expect(
+        await updateArtifacts({
+          packageFileName: 'devbox.json',
+          newPackageFileContent: devboxJson,
+          updatedDeps: [{}],
+          config: {},
+        }),
+      ).toEqual([
+        {
+          artifactError: {
+            lockFile: 'devbox.lock',
+            stderr: "Cannot read properties of undefined (reading 'stdout')",
+          },
+        },
+      ]);
+    });
+  });
+});
diff --git a/lib/modules/manager/devbox/artifacts.ts b/lib/modules/manager/devbox/artifacts.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c90c6faa3345e6c49e8fdd4abd8e18a74172bcd7
--- /dev/null
+++ b/lib/modules/manager/devbox/artifacts.ts
@@ -0,0 +1,82 @@
+import is from '@sindresorhus/is';
+import { logger } from '../../../logger';
+import { exec } from '../../../util/exec';
+import type { ExecOptions } from '../../../util/exec/types';
+import { getSiblingFileName, readLocalFile } from '../../../util/fs';
+import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
+
+export async function updateArtifacts(
+  updateConfig: UpdateArtifact,
+): Promise<UpdateArtifactsResult[] | null> {
+  const lockFileName = getSiblingFileName(
+    updateConfig.packageFileName,
+    'devbox.lock',
+  );
+  const existingLockFileContent = await readLocalFile(lockFileName, 'utf8');
+  if (!existingLockFileContent) {
+    logger.debug('No devbox.lock found');
+    return null;
+  }
+  const execOptions: ExecOptions = {
+    cwdFile: updateConfig.packageFileName,
+    toolConstraints: [
+      {
+        toolName: 'devbox',
+        constraint: updateConfig.config.constraints?.devbox,
+      },
+    ],
+    docker: {},
+    userConfiguredEnv: updateConfig.config.env,
+  };
+
+  let cmd = '';
+  if (
+    updateConfig.config.isLockFileMaintenance ||
+    updateConfig.config.updateType === 'lockFileMaintenance'
+  ) {
+    cmd += 'devbox update';
+  } else if (is.nonEmptyArray(updateConfig.updatedDeps)) {
+    cmd += 'devbox install';
+  } else {
+    logger.trace('No updated devbox packages - returning null');
+    return null;
+  }
+
+  const oldLockFileContent = await readLocalFile(lockFileName);
+  if (!oldLockFileContent) {
+    logger.trace(`No ${lockFileName} found`);
+    return null;
+  }
+
+  try {
+    await exec(cmd, execOptions);
+    const newLockFileContent = await readLocalFile(lockFileName);
+
+    if (
+      !newLockFileContent ||
+      Buffer.compare(oldLockFileContent, newLockFileContent) === 0
+    ) {
+      return null;
+    }
+    logger.trace('Returning updated devbox.lock');
+    return [
+      {
+        file: {
+          type: 'addition',
+          path: lockFileName,
+          contents: newLockFileContent,
+        },
+      },
+    ];
+  } catch (err) {
+    logger.warn({ err }, 'Error updating devbox.lock');
+    return [
+      {
+        artifactError: {
+          lockFile: lockFileName,
+          stderr: err.message,
+        },
+      },
+    ];
+  }
+}
diff --git a/lib/modules/manager/devbox/extract.spec.ts b/lib/modules/manager/devbox/extract.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e97a2f470c25354ab387f21ca6a5c415e89ca66f
--- /dev/null
+++ b/lib/modules/manager/devbox/extract.spec.ts
@@ -0,0 +1,311 @@
+import { codeBlock } from 'common-tags';
+import { extractPackageFile } from '.';
+
+describe('modules/manager/devbox/extract', () => {
+  describe('extractPackageFile', () => {
+    it('returns null when the devbox JSON file is empty', () => {
+      const result = extractPackageFile('', 'devbox.lock');
+      expect(result).toBeNull();
+    });
+
+    it('returns null when the devbox JSON file is malformed', () => {
+      const result = extractPackageFile('malformed json}}}}}', 'devbox.lock');
+      expect(result).toBeNull();
+    });
+
+    it('returns null when the devbox JSON file has no packages', () => {
+      const result = extractPackageFile('{}', 'devbox.lock');
+      expect(result).toBeNull();
+    });
+
+    it('returns a package dependency when the devbox JSON file has a single package', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": ["nodejs@20.1.8"]
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+        ],
+      });
+    });
+
+    it('returns a package dependency when the devbox JSON file has a single package with a version object', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": {
+            "nodejs": "20.1.8"
+          }
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+        ],
+      });
+    });
+
+    it('returns invalid-version when the devbox JSON file has a single package with an invalid version', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": {
+            "nodejs": "^20.1.8"
+          }
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            currentValue: '^20.1.8',
+            datasource: 'devbox',
+            depName: 'nodejs',
+            packageName: 'nodejs',
+            skipReason: 'invalid-version',
+          },
+        ],
+      });
+    });
+
+    it('returns a package dependency when the devbox JSON file has multiple packages', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": ["nodejs@20.1.8", "yarn@1.22.10"]
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+          {
+            depName: 'yarn',
+            currentValue: '1.22.10',
+            datasource: 'devbox',
+            packageName: 'yarn',
+          },
+        ],
+      });
+    });
+
+    it('returns a package dependency when the devbox JSON file has multiple packages with in a packages object', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": {
+            "nodejs": "20.1.8",
+            "yarn": "1.22.10"
+          }
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+          {
+            depName: 'yarn',
+            currentValue: '1.22.10',
+            datasource: 'devbox',
+            packageName: 'yarn',
+          },
+        ],
+      });
+    });
+
+    it('returns a package dependency when the devbox JSON file has multiple packages with package objects', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": {
+            "nodejs": {
+              "version": "20.1.8"
+            },
+            "yarn": {
+              "version": "1.22.10"
+            }
+          }
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+          {
+            depName: 'yarn',
+            currentValue: '1.22.10',
+            datasource: 'devbox',
+            packageName: 'yarn',
+          },
+        ],
+      });
+    });
+
+    it('returns invalid dependencies', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": {
+            "nodejs": "20.1.8",
+            "yarn": "1.22.10",
+            "invalid": "invalid"
+          }
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+          {
+            depName: 'yarn',
+            currentValue: '1.22.10',
+            datasource: 'devbox',
+            packageName: 'yarn',
+          },
+          {
+            currentValue: 'invalid',
+            datasource: 'devbox',
+            depName: 'invalid',
+            packageName: 'invalid',
+            skipReason: 'invalid-version',
+          },
+        ],
+      });
+    });
+
+    it('returns invalid dependencies with package objects', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": {
+            "nodejs": "20.1.8",
+            "yarn": "1.22.10",
+            "invalid": {
+              "version": "invalid"
+            }
+          }
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+          {
+            depName: 'yarn',
+            currentValue: '1.22.10',
+            datasource: 'devbox',
+            packageName: 'yarn',
+          },
+          {
+            currentValue: 'invalid',
+            datasource: 'devbox',
+            depName: 'invalid',
+            packageName: 'invalid',
+            skipReason: 'invalid-version',
+          },
+        ],
+      });
+    });
+
+    it('returns invalid dependencies from the packages array', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": ["nodejs@20.1.8", "yarn@1.22.10", "invalid@invalid", "invalid2"]
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toEqual({
+        deps: [
+          {
+            depName: 'nodejs',
+            currentValue: '20.1.8',
+            datasource: 'devbox',
+            packageName: 'nodejs',
+          },
+          {
+            depName: 'yarn',
+            currentValue: '1.22.10',
+            datasource: 'devbox',
+            packageName: 'yarn',
+          },
+          {
+            currentValue: 'invalid',
+            datasource: 'devbox',
+            depName: 'invalid',
+            packageName: 'invalid',
+            skipReason: 'invalid-version',
+          },
+          {
+            currentValue: undefined,
+            datasource: 'devbox',
+            depName: 'invalid2',
+            packageName: 'invalid2',
+            skipReason: 'not-a-version',
+          },
+        ],
+      });
+    });
+
+    it('returns null if there are no dependencies', () => {
+      const result = extractPackageFile(
+        codeBlock`
+        {
+          "packages": []
+        }
+      `,
+        'devbox.lock',
+      );
+      expect(result).toBeNull();
+    });
+  });
+});
diff --git a/lib/modules/manager/devbox/extract.ts b/lib/modules/manager/devbox/extract.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ab557ce7ea4b9e68a8524e092cef02a0116c6de5
--- /dev/null
+++ b/lib/modules/manager/devbox/extract.ts
@@ -0,0 +1,27 @@
+import { logger } from '../../../logger';
+import type { PackageFileContent } from '../types';
+import { DevboxFileSchema } from './schema';
+
+export function extractPackageFile(
+  content: string,
+  packageFile: string,
+): PackageFileContent | null {
+  logger.trace('devbox.extractPackageFile()');
+
+  const parsedFile = DevboxFileSchema.safeParse(content);
+  if (parsedFile.error) {
+    logger.debug(
+      { packageFile, error: parsedFile.error },
+      'Error parsing devbox.json',
+    );
+    return null;
+  }
+
+  const deps = parsedFile.data.packages;
+
+  if (deps.length) {
+    return { deps };
+  }
+
+  return null;
+}
diff --git a/lib/modules/manager/devbox/index.ts b/lib/modules/manager/devbox/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d6764af34e43efa40d167584860c65c4154cb8e6
--- /dev/null
+++ b/lib/modules/manager/devbox/index.ts
@@ -0,0 +1,12 @@
+import { DevboxDatasource } from '../../datasource/devbox';
+
+export { extractPackageFile } from './extract';
+export { updateArtifacts } from './artifacts';
+
+export const supportsLockFileMaintenance = true;
+
+export const defaultConfig = {
+  fileMatch: ['(^|/)devbox\\.json$'],
+};
+
+export const supportedDatasources = [DevboxDatasource.id];
diff --git a/lib/modules/manager/devbox/readme.md b/lib/modules/manager/devbox/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..5b91520f9fed8bbadbcb5b1c739b0b876ba9d587
--- /dev/null
+++ b/lib/modules/manager/devbox/readme.md
@@ -0,0 +1,5 @@
+Used for updating [devbox](https://www.jetify.com/devbox) projects.
+
+Devbox is a tool for creating isolated, reproducible development environments that run anywhere.
+
+It uses nix packages sourced from the devbox package registry.
diff --git a/lib/modules/manager/devbox/schema.spec.ts b/lib/modules/manager/devbox/schema.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..02bd8b97ac537f214839d6607e1fc47b0d0312ee
--- /dev/null
+++ b/lib/modules/manager/devbox/schema.spec.ts
@@ -0,0 +1,69 @@
+import { DevboxSchema } from './schema';
+
+describe('modules/manager/devbox/schema', () => {
+  it('parses devbox.json with empty packages', () => {
+    expect(DevboxSchema.parse({ packages: {} })).toEqual({
+      packages: [],
+    });
+  });
+
+  it.each([
+    [
+      'parses devbox.json with packages record',
+      { packages: { foo: '1.2.3' } },
+      { packages: { foo: '1.2.3', bar: '1.2.3' } },
+    ],
+    [
+      'parses devbox.json with packages record with version key',
+      {
+        packages: {
+          foo: {
+            version: '1.2.3',
+          },
+        },
+      },
+      {
+        packages: {
+          foo: {
+            version: '1.2.3',
+          },
+          bar: {
+            version: '1.2.3',
+          },
+        },
+      },
+    ],
+    [
+      'parses devbox.json with packages array',
+      { packages: ['foo@1.2.3'] },
+      { packages: ['foo@1.2.3', 'bar@1.2.3'] },
+    ],
+  ])('%s', (_, singleTest, multipleTest) => {
+    expect(DevboxSchema.parse(singleTest)).toEqual({
+      packages: [
+        {
+          currentValue: '1.2.3',
+          datasource: 'devbox',
+          depName: 'foo',
+          packageName: 'foo',
+        },
+      ],
+    });
+    expect(DevboxSchema.parse(multipleTest)).toEqual({
+      packages: [
+        {
+          currentValue: '1.2.3',
+          datasource: 'devbox',
+          depName: 'foo',
+          packageName: 'foo',
+        },
+        {
+          currentValue: '1.2.3',
+          datasource: 'devbox',
+          depName: 'bar',
+          packageName: 'bar',
+        },
+      ],
+    });
+  });
+});
diff --git a/lib/modules/manager/devbox/schema.ts b/lib/modules/manager/devbox/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d96a4eb9417bb34135ed59b52d5dbd28878978db
--- /dev/null
+++ b/lib/modules/manager/devbox/schema.ts
@@ -0,0 +1,68 @@
+import { z } from 'zod';
+import { logger } from '../../../logger';
+import { Jsonc } from '../../../util/schema-utils';
+import { DevboxDatasource } from '../../datasource/devbox';
+import * as devboxVersioning from '../../versioning/devbox';
+import type { PackageDependency } from '../types';
+
+export const DevboxSchema = z.object({
+  packages: z
+    .union([
+      z.array(z.string()).transform((packages) =>
+        packages.reduce(
+          (result, pkg) => {
+            const [name, version] = pkg.split('@');
+            result[name] = version;
+            return result;
+          },
+          {} as Record<string, string | undefined>,
+        ),
+      ),
+      z.record(
+        z.union([
+          z.string(),
+          z.object({ version: z.string() }).transform(({ version }) => version),
+        ]),
+      ),
+    ])
+    .transform((packages): PackageDependency[] =>
+      Object.entries(packages)
+        .map(([pkgName, pkgVer]) => getDep(pkgName, pkgVer))
+        .filter((pkgDep): pkgDep is PackageDependency => !!pkgDep),
+    ),
+});
+
+export const DevboxFileSchema = Jsonc.pipe(DevboxSchema);
+
+function getDep(
+  packageName: string,
+  version: string | undefined,
+): PackageDependency {
+  const dep = {
+    depName: packageName,
+    currentValue: version,
+    datasource: DevboxDatasource.id,
+    packageName,
+  };
+  if (!dep.currentValue) {
+    logger.trace(
+      { packageName },
+      'Skipping devbox dependency with no version in devbox JSON file.',
+    );
+    return {
+      ...dep,
+      skipReason: 'not-a-version',
+    };
+  }
+  if (!devboxVersioning.api.isValid(dep.currentValue)) {
+    logger.trace(
+      { packageName },
+      'Skipping invalid devbox dependency in devbox JSON file.',
+    );
+    return {
+      ...dep,
+      skipReason: 'invalid-version',
+    };
+  }
+  return dep;
+}