diff --git a/lib/manager/cargo/artifacts.ts b/lib/manager/cargo/artifacts.ts
index 8b5ba74afa7ebd843dc4ecb23a0ab07914148a74..0aa1c7570f8f351d99dc1cfef3569fa846739d29 100644
--- a/lib/manager/cargo/artifacts.ts
+++ b/lib/manager/cargo/artifacts.ts
@@ -1,8 +1,6 @@
-import { exec } from '../../util/exec';
-import { getChildProcessEnv } from '../../util/exec/env';
+import { exec, ExecOptions } from '../../util/exec';
 import { logger } from '../../logger';
 import { UpdateArtifact, UpdateArtifactsResult } from '../common';
-import { BinarySource } from '../../util/exec/common';
 import {
   getSiblingFileName,
   readLocalFile,
@@ -29,33 +27,18 @@ export async function updateArtifacts({
   try {
     await writeLocalFile(packageFileName, newPackageFileContent);
     logger.debug('Updating ' + lockFileName);
-    const cwd = config.localDir;
-    const env = getChildProcessEnv();
     for (let i = 0; i < updatedDeps.length; i += 1) {
       const dep = updatedDeps[i];
       // Update dependency `${dep}` in Cargo.lock file corresponding to Cargo.toml file located
       // at ${localPackageFileName} path
-      let cmd: string;
-      if (config.binarySource === BinarySource.Docker) {
-        logger.info('Running cargo via docker');
-        cmd = `docker run --rm `;
-        if (config.dockerUser) {
-          cmd += `--user=${config.dockerUser} `;
-        }
-        const volumes = [cwd];
-        cmd += volumes.map(v => `-v "${v}":"${v}" `).join('');
-        cmd += `-w "${cwd}" `;
-        cmd += `renovate/rust cargo`;
-      } else {
-        logger.info('Running cargo via global cargo');
-        cmd = 'cargo';
-      }
-      cmd += ` update --manifest-path ${packageFileName} --package ${dep}`;
+      let cmd = `cargo update --manifest-path ${packageFileName} --package ${dep}`;
+      const execOptions: ExecOptions = {
+        docker: {
+          image: 'renovate/rust',
+        },
+      };
       try {
-        await exec(cmd, {
-          cwd,
-          env,
-        });
+        await exec(cmd, execOptions);
       } catch (err) /* istanbul ignore next */ {
         // Two different versions of one dependency can be present in the same
         // crate, and when that happens an attempt to update it with --package ${dep}
@@ -71,10 +54,7 @@ export async function updateArtifacts({
         const msgStart = 'error: There are multiple';
         if (err.code === 101 && err.stderr.startsWith(msgStart)) {
           cmd = cmd.replace(/ --package.*/, '');
-          await exec(cmd, {
-            cwd,
-            env,
-          });
+          await exec(cmd, execOptions);
         } else {
           throw err; // this is caught below
         }
diff --git a/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap b/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap
index 025d208444b3bc4ca5ed98f0bce5518ae4daaa1b..03acfd59774a2592dc54634ad7bc8add7d66f8c3 100644
--- a/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap
@@ -52,7 +52,13 @@ Array [
 exports[`.updateArtifacts() returns updated Cargo.lock with docker 1`] = `
 Array [
   Object {
-    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/rust cargo update --manifest-path Cargo.toml --package dep1",
+    "cmd": "docker pull renovate/rust",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/rust bash -l -c \\"cargo update --manifest-path Cargo.toml --package dep1\\"",
     "options": Object {
       "cwd": "/tmp/github/some/repo",
       "encoding": "utf-8",
diff --git a/test/manager/cargo/artifacts.spec.ts b/test/manager/cargo/artifacts.spec.ts
index 4b92d03c4cf62ba3b35bf74cdb55597c7fb93844..e7faa0f00ccc6a5aee35740c49414b9c26d8c6e4 100644
--- a/test/manager/cargo/artifacts.spec.ts
+++ b/test/manager/cargo/artifacts.spec.ts
@@ -1,3 +1,4 @@
+import { join } from 'upath';
 import _fs from 'fs-extra';
 import { exec as _exec } from 'child_process';
 import * as cargo from '../../../lib/manager/cargo/artifacts';
@@ -5,7 +6,9 @@ import { platform as _platform } from '../../../lib/platform';
 import { mocked } from '../../util';
 import { envMock, mockExecAll } from '../../execUtil';
 import * as _env from '../../../lib/util/exec/env';
+import { setExecConfig } from '../../../lib/util/exec';
 import { BinarySource } from '../../../lib/util/exec/common';
+import { resetPrefetchedImages } from '../../../lib/util/exec/docker';
 
 jest.mock('fs-extra');
 jest.mock('child_process');
@@ -17,7 +20,9 @@ const env = mocked(_env);
 const platform = mocked(_platform);
 
 const config = {
-  localDir: '/tmp/github/some/repo',
+  // `join` fixes Windows CI
+  localDir: join('/tmp/github/some/repo'),
+  dockerUser: 'foobar',
 };
 
 describe('.updateArtifacts()', () => {
@@ -26,6 +31,8 @@ describe('.updateArtifacts()', () => {
     jest.resetModules();
 
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
+    setExecConfig(config);
+    resetPrefetchedImages();
   });
   it('returns null if no Cargo.lock found', async () => {
     const updatedDeps = ['dep1'];
@@ -79,6 +86,7 @@ describe('.updateArtifacts()', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated Cargo.lock with docker', async () => {
+    setExecConfig({ ...config, binarySource: BinarySource.Docker });
     platform.getFile.mockResolvedValueOnce('Old Cargo.lock');
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockResolvedValueOnce('New Cargo.lock' as any);
@@ -88,11 +96,7 @@ describe('.updateArtifacts()', () => {
         packageFileName: 'Cargo.toml',
         updatedDeps,
         newPackageFileContent: '{}',
-        config: {
-          ...config,
-          binarySource: BinarySource.Docker,
-          dockerUser: 'foobar',
-        },
+        config,
       })
     ).not.toBeNull();
     expect(execSnapshots).toMatchSnapshot();