diff --git a/lib/manager/helmv3/__snapshots__/artifacts.spec.ts.snap b/lib/manager/helmv3/__snapshots__/artifacts.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..da0ed762c322695c73821027ba202436be8f4989
--- /dev/null
+++ b/lib/manager/helmv3/__snapshots__/artifacts.spec.ts.snap
@@ -0,0 +1,116 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`.updateArtifacts() catches errors 1`] = `
+Array [
+  Object {
+    "artifactError": Object {
+      "lockFile": "Chart.lock",
+      "stderr": "not found",
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "helm dependency update ''",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "LANG": "en_US.UTF-8",
+        "LC_ALL": "en_US",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+      "maxBuffer": 10485760,
+      "timeout": 900000,
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Chart.lock 1`] = `
+Array [
+  Object {
+    "cmd": "helm dependency update ''",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "LANG": "en_US.UTF-8",
+        "LC_ALL": "en_US",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+      "maxBuffer": 10485760,
+      "timeout": 900000,
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Chart.lock for lockfile maintenance 1`] = `
+Array [
+  Object {
+    "cmd": "helm dependency update ''",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "LANG": "en_US.UTF-8",
+        "LC_ALL": "en_US",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+      "maxBuffer": 10485760,
+      "timeout": 900000,
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Chart.lock with docker 1`] = `
+Array [
+  Object {
+    "cmd": "docker pull renovate/helm",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "docker ps --filter name=renovate_helm -aq",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "docker run --rm --name=renovate_helm --label=renovate_child --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/helm bash -l -c \\"helm dependency update ''\\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "LANG": "en_US.UTF-8",
+        "LC_ALL": "en_US",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+      "maxBuffer": 10485760,
+      "timeout": 900000,
+    },
+  },
+]
+`;
diff --git a/lib/manager/helmv3/artifacts.spec.ts b/lib/manager/helmv3/artifacts.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c2723973b7c3992e1c67d33ae086074678d6f4a6
--- /dev/null
+++ b/lib/manager/helmv3/artifacts.spec.ts
@@ -0,0 +1,136 @@
+import { exec as _exec } from 'child_process';
+import _fs from 'fs-extra';
+import { join } from 'upath';
+import { envMock, mockExecAll } from '../../../test/execUtil';
+import { git, mocked } from '../../../test/util';
+import { setExecConfig } from '../../util/exec';
+import { BinarySource } from '../../util/exec/common';
+import * as docker from '../../util/exec/docker';
+import * as _env from '../../util/exec/env';
+import * as helmv3 from './artifacts';
+
+jest.mock('fs-extra');
+jest.mock('child_process');
+jest.mock('../../util/exec/env');
+jest.mock('../../util/git');
+jest.mock('../../util/http');
+
+const fs: jest.Mocked<typeof _fs> = _fs as any;
+const exec: jest.Mock<typeof _exec> = _exec as any;
+const env = mocked(_env);
+
+const config = {
+  // `join` fixes Windows CI
+  localDir: join('/tmp/github/some/repo'),
+  dockerUser: 'foobar',
+};
+
+describe('.updateArtifacts()', () => {
+  beforeEach(async () => {
+    jest.resetAllMocks();
+    jest.resetModules();
+
+    env.getChildProcessEnv.mockReturnValue(envMock.basic);
+    await setExecConfig(config);
+    docker.resetPrefetchedImages();
+  });
+  it('returns null if no Chart.lock found', async () => {
+    const updatedDeps = ['dep1'];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: '',
+        config,
+      })
+    ).toBeNull();
+  });
+  it('returns null if updatedDeps is empty', async () => {
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps: [],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toBeNull();
+  });
+  it('returns null if unchanged', async () => {
+    fs.readFile.mockResolvedValueOnce('Current Chart.lock' as any);
+    const execSnapshots = mockExecAll(exec);
+    fs.readFile.mockResolvedValueOnce('Current Chart.lock' as any);
+    const updatedDeps = ['dep1'];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: '',
+        config,
+      })
+    ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('returns updated Chart.lock', async () => {
+    git.getFile.mockResolvedValueOnce('Old Chart.lock');
+    const execSnapshots = mockExecAll(exec);
+    fs.readFile.mockResolvedValueOnce('New Chart.lock' as any);
+    const updatedDeps = ['dep1'];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: '{}',
+        config,
+      })
+    ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+
+  it('returns updated Chart.lock for lockfile maintenance', async () => {
+    git.getFile.mockResolvedValueOnce('Old Chart.lock');
+    const execSnapshots = mockExecAll(exec);
+    fs.readFile.mockResolvedValueOnce('New Chart.lock' as any);
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps: [],
+        newPackageFileContent: '{}',
+        config: { ...config, updateType: 'lockFileMaintenance' },
+      })
+    ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+
+  it('returns updated Chart.lock with docker', async () => {
+    jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce();
+    await setExecConfig({ ...config, binarySource: BinarySource.Docker });
+    git.getFile.mockResolvedValueOnce('Old Chart.lock');
+    const execSnapshots = mockExecAll(exec);
+    fs.readFile.mockResolvedValueOnce('New Chart.lock' as any);
+    const updatedDeps = ['dep1'];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: '{}',
+        config,
+      })
+    ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('catches errors', async () => {
+    fs.readFile.mockResolvedValueOnce('Current Chart.lock' as any);
+    fs.outputFile.mockImplementationOnce(() => {
+      throw new Error('not found');
+    });
+    const updatedDeps = ['dep1'];
+    expect(
+      await helmv3.updateArtifacts({
+        packageFileName: 'Chart.yaml',
+        updatedDeps,
+        newPackageFileContent: '{}',
+        config,
+      })
+    ).toMatchSnapshot();
+  });
+});
diff --git a/lib/manager/helmv3/artifacts.ts b/lib/manager/helmv3/artifacts.ts
new file mode 100644
index 0000000000000000000000000000000000000000..92968ae2b9d35c301b58a655c5f27c6b8d55477a
--- /dev/null
+++ b/lib/manager/helmv3/artifacts.ts
@@ -0,0 +1,76 @@
+import { quote } from 'shlex';
+import { logger } from '../../logger';
+import { ExecOptions, exec } from '../../util/exec';
+import {
+  getSiblingFileName,
+  getSubDirectory,
+  readLocalFile,
+  writeLocalFile,
+} from '../../util/fs';
+import { UpdateArtifact, UpdateArtifactsResult } from '../common';
+
+async function helmUpdate(manifestPath: string): Promise<void> {
+  const cmd = `helm dependency update ${quote(getSubDirectory(manifestPath))}`;
+
+  const execOptions: ExecOptions = {
+    docker: {
+      image: 'renovate/helm',
+    },
+  };
+  await exec(cmd, execOptions);
+}
+
+export async function updateArtifacts({
+  packageFileName,
+  updatedDeps,
+  newPackageFileContent,
+  config,
+}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
+  logger.debug(`helmv3.updateArtifacts(${packageFileName})`);
+
+  const isLockFileMaintenance = config.updateType === 'lockFileMaintenance';
+
+  if (
+    !isLockFileMaintenance &&
+    (updatedDeps === undefined || updatedDeps.length < 1)
+  ) {
+    logger.debug('No updated helmv3 deps - returning null');
+    return null;
+  }
+
+  const lockFileName = getSiblingFileName(packageFileName, 'Chart.lock');
+  const existingLockFileContent = await readLocalFile(lockFileName);
+  if (!existingLockFileContent) {
+    logger.debug('No Chart.lock found');
+    return null;
+  }
+  try {
+    await writeLocalFile(packageFileName, newPackageFileContent);
+    logger.debug('Updating ' + lockFileName);
+    await helmUpdate(packageFileName);
+    logger.debug('Returning updated Chart.lock');
+    const newHelmLockContent = await readLocalFile(lockFileName);
+    if (existingLockFileContent === newHelmLockContent) {
+      logger.debug('Chart.lock is unchanged');
+      return null;
+    }
+    return [
+      {
+        file: {
+          name: lockFileName,
+          contents: newHelmLockContent,
+        },
+      },
+    ];
+  } catch (err) {
+    logger.warn({ err }, 'Failed to update Helm lock file');
+    return [
+      {
+        artifactError: {
+          lockFile: lockFileName,
+          stderr: err.message,
+        },
+      },
+    ];
+  }
+}
diff --git a/lib/manager/helmv3/index.ts b/lib/manager/helmv3/index.ts
index 0660e2e055846b79d4fcc18952d1eddbc1bd17ae..0152904b54d2f399447b335df06a9c0d0d766d01 100644
--- a/lib/manager/helmv3/index.ts
+++ b/lib/manager/helmv3/index.ts
@@ -1,5 +1,8 @@
+export { updateArtifacts } from './artifacts';
 export { extractPackageFile } from './extract';
 
+export const supportsLockFileMaintenance = true;
+
 export const defaultConfig = {
   aliases: {
     stable: 'https://kubernetes-charts.storage.googleapis.com/',