diff --git a/Dockerfile b/Dockerfile
index ba90e46f8ddb1f923faf45e330d60c141aa13baa..c8da0e0027ce2cae2351b234b27d9b2ed15f49f3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -161,6 +161,12 @@ RUN rm -rf /usr/bin/python && ln /usr/bin/python3.8 /usr/bin/python
 
 RUN curl --silent https://bootstrap.pypa.io/get-pip.py | python
 
+# CocoaPods
+RUN apt-get update && apt-get install -y ruby ruby2.5-dev && rm -rf /var/lib/apt/lists/*
+RUN ruby --version
+ENV COCOAPODS_VERSION 1.9.0
+RUN gem install --no-rdoc --no-ri cocoapods -v ${COCOAPODS_VERSION}
+
 # Set up ubuntu user and home directory with access to users in the root group (0)
 
 ENV HOME=/home/ubuntu
diff --git a/lib/datasource/pod/index.spec.ts b/lib/datasource/pod/index.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a611fe2f3e8f748554d419c92b1157d64e6eab1d
--- /dev/null
+++ b/lib/datasource/pod/index.spec.ts
@@ -0,0 +1,133 @@
+import { api as _api } from '../../platform/github/gh-got-wrapper';
+import { getPkgReleases } from '.';
+import { mocked } from '../../../test/util';
+import { GotResponse } from '../../platform';
+import { GetReleasesConfig } from '../common';
+
+const api = mocked(_api);
+
+jest.mock('../../platform/github/gh-got-wrapper');
+
+const config = {
+  lookupName: 'foo',
+  registryUrls: ['https://github.com/CocoaPods/Specs'],
+};
+
+describe('datasource/cocoapods', () => {
+  describe('getPkgReleases', () => {
+    beforeEach(() => global.renovateCache.rmAll());
+    it('returns null for invalid inputs', async () => {
+      api.get.mockResolvedValueOnce(null);
+      expect(
+        await getPkgReleases({ registryUrls: [] } as GetReleasesConfig)
+      ).toBeNull();
+      expect(
+        await getPkgReleases({
+          lookupName: null,
+        })
+      ).toBeNull();
+      expect(
+        await getPkgReleases({
+          lookupName: 'foobar',
+          registryUrls: [],
+        })
+      ).toBeNull();
+    });
+    it('returns null for empty result', async () => {
+      api.get.mockResolvedValueOnce(null);
+      expect(await getPkgReleases(config)).toBeNull();
+    });
+    it('returns null for missing fields', async () => {
+      api.get.mockResolvedValueOnce({} as GotResponse);
+      expect(await getPkgReleases(config)).toBeNull();
+
+      api.get.mockResolvedValueOnce({ body: '' } as GotResponse);
+      expect(await getPkgReleases(config)).toBeNull();
+    });
+    it('returns null for 404', async () => {
+      api.get.mockImplementation(() =>
+        Promise.reject({
+          statusCode: 404,
+        })
+      );
+      expect(
+        await getPkgReleases({
+          ...config,
+          registryUrls: [
+            ...config.registryUrls,
+            'invalid',
+            'https://github.com/foo/bar',
+          ],
+        })
+      ).toBeNull();
+    });
+    it('returns null for 401', async () => {
+      api.get.mockImplementationOnce(() =>
+        Promise.reject({
+          statusCode: 401,
+        })
+      );
+      expect(await getPkgReleases(config)).toBeNull();
+    });
+    it('throws for 429', async () => {
+      api.get.mockImplementationOnce(() =>
+        Promise.reject({
+          statusCode: 429,
+        })
+      );
+      await expect(getPkgReleases(config)).rejects.toThrowError(
+        'registry-failure'
+      );
+    });
+    it('throws for 5xx', async () => {
+      api.get.mockImplementationOnce(() =>
+        Promise.reject({
+          statusCode: 502,
+        })
+      );
+      await expect(getPkgReleases(config)).rejects.toThrowError(
+        'registry-failure'
+      );
+    });
+    it('returns null for unknown error', async () => {
+      api.get.mockImplementationOnce(() => {
+        throw new Error();
+      });
+      expect(await getPkgReleases(config)).toBeNull();
+    });
+    it('processes real data from CDN', async () => {
+      api.get.mockResolvedValueOnce({
+        body: 'foo/1.2.3',
+      } as GotResponse);
+      expect(
+        await getPkgReleases({
+          ...config,
+          registryUrls: ['https://cdn.cocoapods.org'],
+        })
+      ).toEqual({
+        releases: [
+          {
+            version: '1.2.3',
+          },
+        ],
+      });
+    });
+    it('processes real data from Github', async () => {
+      api.get.mockResolvedValueOnce({
+        body: [{ name: '1.2.3' }],
+      } as GotResponse);
+      expect(
+        await getPkgReleases({
+          ...config,
+          registryUrls: ['https://github.com/Artsy/Specs'],
+        })
+      ).toEqual({
+        releases: [
+          {
+            version: '1.2.3',
+          },
+        ],
+      });
+    });
+  });
+});
diff --git a/lib/datasource/pod/index.ts b/lib/datasource/pod/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0cb4541b6a3ac7697469d266db9b4ff7f0ea394
--- /dev/null
+++ b/lib/datasource/pod/index.ts
@@ -0,0 +1,170 @@
+import crypto from 'crypto';
+import { api } from '../../platform/github/gh-got-wrapper';
+import { GetReleasesConfig, ReleaseResult } from '../common';
+import { logger } from '../../logger';
+
+export const id = 'pod';
+
+const cacheNamespace = `datasource-${id}`;
+const cacheMinutes = 30;
+
+function shardParts(lookupName: string): string[] {
+  return crypto
+    .createHash('md5')
+    .update(lookupName)
+    .digest('hex')
+    .slice(0, 3)
+    .split('');
+}
+
+function releasesGithubUrl(
+  lookupName: string,
+  opts: { account: string; repo: string; useShard: boolean }
+): string {
+  const { useShard, account, repo } = opts;
+  const prefix = 'https://api.github.com/repos';
+  const shard = shardParts(lookupName).join('/');
+  const suffix = useShard ? `${shard}/${lookupName}` : lookupName;
+  return `${prefix}/${account}/${repo}/contents/Specs/${suffix}`;
+}
+
+async function makeRequest<T = unknown>(
+  url: string,
+  lookupName: string,
+  json = true
+): Promise<T | null> {
+  try {
+    const resp = await api.get(url, { json });
+    if (resp && resp.body) {
+      return resp.body;
+    }
+  } catch (err) {
+    const errorData = { lookupName, err };
+
+    if (
+      err.statusCode === 429 ||
+      (err.statusCode >= 500 && err.statusCode < 600)
+    ) {
+      logger.warn({ lookupName, err }, `CocoaPods registry failure`);
+      throw new Error('registry-failure');
+    }
+
+    if (err.statusCode === 401) {
+      logger.debug(errorData, 'Authorization error');
+    } else if (err.statusCode === 404) {
+      logger.debug(errorData, 'Package lookup error');
+    } else {
+      logger.warn(errorData, 'CocoaPods lookup failure: Unknown error');
+    }
+  }
+
+  return null;
+}
+
+const githubRegex = /^https:\/\/github\.com\/(?<account>[^/]+)\/(?<repo>[^/]+?)(\.git|\/.*)?$/;
+
+async function getReleasesFromGithub(
+  lookupName: string,
+  registryUrl: string,
+  useShard = false
+): Promise<ReleaseResult | null> {
+  const match = githubRegex.exec(registryUrl);
+  const { account, repo } = (match && match.groups) || {};
+  const opts = { account, repo, useShard };
+  const url = releasesGithubUrl(lookupName, opts);
+  const resp = await makeRequest<{ name: string }[]>(url, lookupName);
+  if (resp) {
+    const releases = resp.map(({ name }) => ({ version: name }));
+    return { releases };
+  }
+
+  if (!useShard) {
+    return getReleasesFromGithub(lookupName, registryUrl, true);
+  }
+
+  return null;
+}
+
+function releasesCDNUrl(lookupName: string, registryUrl: string): string {
+  const shard = shardParts(lookupName).join('_');
+  return `${registryUrl}/all_pods_versions_${shard}.txt`;
+}
+
+async function getReleasesFromCDN(
+  lookupName: string,
+  registryUrl: string
+): Promise<ReleaseResult | null> {
+  const url = releasesCDNUrl(lookupName, registryUrl);
+  const resp = await makeRequest<string>(url, lookupName, false);
+  if (resp) {
+    const lines = resp.split('\n');
+    for (let idx = 0; idx < lines.length; idx += 1) {
+      const line = lines[idx];
+      const [name, ...versions] = line.split('/');
+      if (name === lookupName.replace(/\/.*$/, '')) {
+        const releases = versions.map(version => ({ version }));
+        return { releases };
+      }
+    }
+  }
+  return null;
+}
+
+const defaultCDN = 'https://cdn.cocoapods.org';
+
+function isDefaultRepo(url: string): boolean {
+  const match = githubRegex.exec(url);
+  if (match) {
+    const { account, repo } = match.groups || {};
+    return (
+      account.toLowerCase() === 'cocoapods' && repo.toLowerCase() === 'specs'
+    ); // https://github.com/CocoaPods/Specs.git
+  }
+  return false;
+}
+
+export async function getPkgReleases(
+  config: GetReleasesConfig
+): Promise<ReleaseResult | null> {
+  const { lookupName } = config;
+  let { registryUrls } = config;
+  registryUrls =
+    registryUrls && registryUrls.length ? registryUrls : [defaultCDN];
+
+  if (!lookupName) {
+    logger.debug(config, `CocoaPods: invalid lookup name`);
+    return null;
+  }
+
+  const podName = lookupName.replace(/\/.*$/, '');
+
+  const cachedResult = await renovateCache.get<ReleaseResult>(
+    cacheNamespace,
+    podName
+  );
+  /* istanbul ignore next line */
+  if (cachedResult) {
+    logger.debug(`CocoaPods: Return cached result for ${podName}`);
+    return cachedResult;
+  }
+
+  let result: ReleaseResult | null = null;
+  for (let idx = 0; !result && idx < registryUrls.length; idx += 1) {
+    let registryUrl = registryUrls[idx].replace(/\/+$/, '');
+
+    // In order to not abuse github API limits, query CDN instead
+    if (isDefaultRepo(registryUrl)) registryUrl = defaultCDN;
+
+    if (githubRegex.exec(registryUrl)) {
+      result = await getReleasesFromGithub(podName, registryUrl);
+    } else {
+      result = await getReleasesFromCDN(podName, registryUrl);
+    }
+  }
+
+  if (result) {
+    await renovateCache.set(cacheNamespace, podName, result, cacheMinutes);
+  }
+
+  return result;
+}
diff --git a/lib/manager/cocoapods/__fixtures__/Podfile.complex b/lib/manager/cocoapods/__fixtures__/Podfile.complex
new file mode 100644
index 0000000000000000000000000000000000000000..be309bfcd4b48e2585b097cf6f8270c51da728e9
--- /dev/null
+++ b/lib/manager/cocoapods/__fixtures__/Podfile.complex
@@ -0,0 +1,72 @@
+platform :ios, '9.0'
+# use_frameworks!
+inhibit_all_warnings!
+
+source "https://github.com/CocoaPods/Specs.git"
+
+target 'Sample' do
+
+  pod 'IQKeyboardManager', '~> 6.5.0'
+  pod 'CYLTabBarController', '~> 1.28.3'
+
+  pod 'PureLayout', '~> 3.1.4'
+  pod 'AFNetworking/Serialization', '~> 3.2.1'
+  pod 'AFNetworking/Security', '~> 3.2.1'
+  pod 'AFNetworking/Reachability', '~> 3.2.1'
+  pod 'AFNetworking/NSURLSession', '~> 3.2.1'
+  # pod 'SVProgressHUD', '~> 2.2.5'
+  pod 'MBProgressHUD', '~> 1.1.0'
+  pod 'MJRefresh', '~> 3.1.16'
+  pod 'MJExtension', '~> 3.1.0'
+  pod 'TYPagerController', '~> 2.1.2'
+  pod 'YYImage', '~> 1.0.4'
+  pod 'SDWebImage', '~> 5.0'
+  pod 'SDCycleScrollView','~> 1.80'
+  pod 'NullSafe', '~> 2.0'
+  # pod 'ZLPhotoBrowser'
+  pod 'TZImagePickerController', '~> 3.2.1'
+  pod 'TOCropViewController', '~> 2.5.1'
+  # pod 'RSKImageCropper', '~> 2.2.3'
+  # pod 'LBPhotoBrowser', '~> 2.2.2'
+  # pod 'YBImageBrowser', '~> 3.0.3'
+  pod 'FMDB', '~> 2.7.5'
+  pod 'FDStackView', '~> 1.0.1'
+  pod 'LYEmptyView'
+
+  pod 'MMKV', '~> 1.0.22'
+
+  pod 'fishhook'
+
+  pod 'CocoaLumberjack', '~> 3.5.3'
+
+  pod 'GZIP', '~> 1.2'
+
+  pod 'LBXScan/LBXNative','~> 2.3'
+  pod 'LBXScan/LBXZXing','~> 2.3'
+  # pod 'LBXScan/LBXZBar','~> 2.3'
+  pod 'LBXScan/UI','~> 2.3'
+
+  pod 'MLeaksFinder'
+  pod 'FBMemoryProfiler'
+
+  target 'SampleTests' do
+    inherit! :search_paths
+    # Pods for testing
+  end
+
+  target 'SampleUITests' do
+    inherit! :search_paths
+    # Pods for testing
+  end
+
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    target.build_configurations.each do |config|
+      if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f <= 8.0
+        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
+      end
+    end
+  end
+end
diff --git a/lib/manager/cocoapods/__fixtures__/Podfile.simple b/lib/manager/cocoapods/__fixtures__/Podfile.simple
new file mode 100644
index 0000000000000000000000000000000000000000..1c8ffcff3fc15f1d61eea569a4c6a27b8070acda
--- /dev/null
+++ b/lib/manager/cocoapods/__fixtures__/Podfile.simple
@@ -0,0 +1,11 @@
+source 'https://github.com/Artsy/Specs.git'
+
+pod 'a'
+pod 'a/sub'
+pod 'b', '1.2.3'
+pod 'c', "1.2.3"
+pod 'd', :path => '~/Documents/Alamofire'
+pod 'e', :git => 'e.git'
+pod 'f', :git => 'f.git', :branch => 'dev'
+pod 'g', :git => 'g.git', :tag => '3.2.1'
+pod 'h', :git => 'https://github.com/foo/bar.git', :tag => '0.0.1'
diff --git a/lib/manager/cocoapods/__snapshots__/artifacts.spec.ts.snap b/lib/manager/cocoapods/__snapshots__/artifacts.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..c6635e937ae61dfaf44a42be5592314c0009bbd6
--- /dev/null
+++ b/lib/manager/cocoapods/__snapshots__/artifacts.spec.ts.snap
@@ -0,0 +1,167 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`.updateArtifacts() catches write error 1`] = `
+Array [
+  Object {
+    "artifactError": Object {
+      "lockFile": "Podfile.lock",
+      "stderr": "not found",
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() catches write error 2`] = `Array []`;
+
+exports[`.updateArtifacts() dynamically selects Docker image tag 1`] = `
+Array [
+  Object {
+    "cmd": "docker pull renovate/cocoapods:1.2.4",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "docker run --rm --user=ubuntu -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/cocoapods:1.2.4 bash -l -c \\"pod install\\"",
+    "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",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() falls back to the \`latest\` Docker image tag 1`] = `
+Array [
+  Object {
+    "cmd": "docker pull renovate/cocoapods:latest",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "docker run --rm --user=ubuntu -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/cocoapods:latest bash -l -c \\"pod install\\"",
+    "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",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns null for invalid local directory 1`] = `Array []`;
+
+exports[`.updateArtifacts() returns null if no Podfile.lock found 1`] = `Array []`;
+
+exports[`.updateArtifacts() returns null if no updatedDeps were provided 1`] = `Array []`;
+
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "pod install",
+    "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",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns null if updatedDeps is empty 1`] = `Array []`;
+
+exports[`.updateArtifacts() returns pod exec error 1`] = `
+Array [
+  Object {
+    "artifactError": Object {
+      "lockFile": "Podfile.lock",
+      "stderr": "exec exception",
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns pod exec error 2`] = `
+Array [
+  Object {
+    "cmd": "pod install",
+    "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",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Podfile 1`] = `
+Array [
+  Object {
+    "file": Object {
+      "contents": "New Podfile",
+      "name": "Podfile.lock",
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Podfile 2`] = `
+Array [
+  Object {
+    "cmd": "docker pull renovate/cocoapods",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/cocoapods bash -l -c \\"pod install\\"",
+    "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",
+      },
+    },
+  },
+]
+`;
diff --git a/lib/manager/cocoapods/__snapshots__/extract.spec.ts.snap b/lib/manager/cocoapods/__snapshots__/extract.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..003d829b8cc16199f5a9aaa71132645373560f29
--- /dev/null
+++ b/lib/manager/cocoapods/__snapshots__/extract.spec.ts.snap
@@ -0,0 +1,394 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`lib/manager/cocoapods/extract extractPackageFile() extracts all dependencies 1`] = `
+Array [
+  Object {
+    "depName": "a",
+    "groupName": "a",
+    "skipReason": "unknown-version",
+  },
+  Object {
+    "depName": "a/sub",
+    "groupName": "a",
+    "skipReason": "unknown-version",
+  },
+  Object {
+    "currentValue": "1.2.3",
+    "datasource": "pod",
+    "depName": "b",
+    "groupName": "b",
+    "managerData": Object {
+      "lineNumber": 4,
+    },
+    "registryUrls": Array [
+      "https://github.com/Artsy/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "1.2.3",
+    "datasource": "pod",
+    "depName": "c",
+    "groupName": "c",
+    "managerData": Object {
+      "lineNumber": 5,
+    },
+    "registryUrls": Array [
+      "https://github.com/Artsy/Specs.git",
+    ],
+  },
+  Object {
+    "depName": "d",
+    "groupName": "d",
+    "skipReason": "path-dependency",
+  },
+  Object {
+    "depName": "e",
+    "groupName": "e",
+    "skipReason": "git-dependency",
+  },
+  Object {
+    "depName": "f",
+    "groupName": "f",
+    "skipReason": "git-dependency",
+  },
+  Object {
+    "managerData": Object {
+      "lineNumber": 9,
+    },
+  },
+  Object {
+    "currentValue": "0.0.1",
+    "datasource": "github-tags",
+    "depName": "h",
+    "lookupName": "foo/bar",
+    "managerData": Object {
+      "lineNumber": 10,
+    },
+  },
+]
+`;
+
+exports[`lib/manager/cocoapods/extract extractPackageFile() extracts all dependencies 2`] = `
+Array [
+  Object {
+    "currentValue": "~> 6.5.0",
+    "datasource": "pod",
+    "depName": "IQKeyboardManager",
+    "groupName": "IQKeyboardManager",
+    "managerData": Object {
+      "lineNumber": 8,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 1.28.3",
+    "datasource": "pod",
+    "depName": "CYLTabBarController",
+    "groupName": "CYLTabBarController",
+    "managerData": Object {
+      "lineNumber": 9,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.1.4",
+    "datasource": "pod",
+    "depName": "PureLayout",
+    "groupName": "PureLayout",
+    "managerData": Object {
+      "lineNumber": 11,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.2.1",
+    "datasource": "pod",
+    "depName": "AFNetworking/Serialization",
+    "groupName": "AFNetworking",
+    "managerData": Object {
+      "lineNumber": 12,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.2.1",
+    "datasource": "pod",
+    "depName": "AFNetworking/Security",
+    "groupName": "AFNetworking",
+    "managerData": Object {
+      "lineNumber": 13,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.2.1",
+    "datasource": "pod",
+    "depName": "AFNetworking/Reachability",
+    "groupName": "AFNetworking",
+    "managerData": Object {
+      "lineNumber": 14,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.2.1",
+    "datasource": "pod",
+    "depName": "AFNetworking/NSURLSession",
+    "groupName": "AFNetworking",
+    "managerData": Object {
+      "lineNumber": 15,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 1.1.0",
+    "datasource": "pod",
+    "depName": "MBProgressHUD",
+    "groupName": "MBProgressHUD",
+    "managerData": Object {
+      "lineNumber": 17,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.1.16",
+    "datasource": "pod",
+    "depName": "MJRefresh",
+    "groupName": "MJRefresh",
+    "managerData": Object {
+      "lineNumber": 18,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.1.0",
+    "datasource": "pod",
+    "depName": "MJExtension",
+    "groupName": "MJExtension",
+    "managerData": Object {
+      "lineNumber": 19,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 2.1.2",
+    "datasource": "pod",
+    "depName": "TYPagerController",
+    "groupName": "TYPagerController",
+    "managerData": Object {
+      "lineNumber": 20,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 1.0.4",
+    "datasource": "pod",
+    "depName": "YYImage",
+    "groupName": "YYImage",
+    "managerData": Object {
+      "lineNumber": 21,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 5.0",
+    "datasource": "pod",
+    "depName": "SDWebImage",
+    "groupName": "SDWebImage",
+    "managerData": Object {
+      "lineNumber": 22,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 1.80",
+    "datasource": "pod",
+    "depName": "SDCycleScrollView",
+    "groupName": "SDCycleScrollView",
+    "managerData": Object {
+      "lineNumber": 23,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 2.0",
+    "datasource": "pod",
+    "depName": "NullSafe",
+    "groupName": "NullSafe",
+    "managerData": Object {
+      "lineNumber": 24,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 3.2.1",
+    "datasource": "pod",
+    "depName": "TZImagePickerController",
+    "groupName": "TZImagePickerController",
+    "managerData": Object {
+      "lineNumber": 26,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 2.5.1",
+    "datasource": "pod",
+    "depName": "TOCropViewController",
+    "groupName": "TOCropViewController",
+    "managerData": Object {
+      "lineNumber": 27,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 2.7.5",
+    "datasource": "pod",
+    "depName": "FMDB",
+    "groupName": "FMDB",
+    "managerData": Object {
+      "lineNumber": 31,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 1.0.1",
+    "datasource": "pod",
+    "depName": "FDStackView",
+    "groupName": "FDStackView",
+    "managerData": Object {
+      "lineNumber": 32,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "depName": "LYEmptyView",
+    "groupName": "LYEmptyView",
+    "skipReason": "unknown-version",
+  },
+  Object {
+    "currentValue": "~> 1.0.22",
+    "datasource": "pod",
+    "depName": "MMKV",
+    "groupName": "MMKV",
+    "managerData": Object {
+      "lineNumber": 35,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "depName": "fishhook",
+    "groupName": "fishhook",
+    "skipReason": "unknown-version",
+  },
+  Object {
+    "currentValue": "~> 3.5.3",
+    "datasource": "pod",
+    "depName": "CocoaLumberjack",
+    "groupName": "CocoaLumberjack",
+    "managerData": Object {
+      "lineNumber": 39,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 1.2",
+    "datasource": "pod",
+    "depName": "GZIP",
+    "groupName": "GZIP",
+    "managerData": Object {
+      "lineNumber": 41,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 2.3",
+    "datasource": "pod",
+    "depName": "LBXScan/LBXNative",
+    "groupName": "LBXScan",
+    "managerData": Object {
+      "lineNumber": 43,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 2.3",
+    "datasource": "pod",
+    "depName": "LBXScan/LBXZXing",
+    "groupName": "LBXScan",
+    "managerData": Object {
+      "lineNumber": 44,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "currentValue": "~> 2.3",
+    "datasource": "pod",
+    "depName": "LBXScan/UI",
+    "groupName": "LBXScan",
+    "managerData": Object {
+      "lineNumber": 46,
+    },
+    "registryUrls": Array [
+      "https://github.com/CocoaPods/Specs.git",
+    ],
+  },
+  Object {
+    "depName": "MLeaksFinder",
+    "groupName": "MLeaksFinder",
+    "skipReason": "unknown-version",
+  },
+  Object {
+    "depName": "FBMemoryProfiler",
+    "groupName": "FBMemoryProfiler",
+    "skipReason": "unknown-version",
+  },
+]
+`;
diff --git a/lib/manager/cocoapods/artifacts.spec.ts b/lib/manager/cocoapods/artifacts.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..13726577b00cb9f0d6ed34a33c0700a0bd76b814
--- /dev/null
+++ b/lib/manager/cocoapods/artifacts.spec.ts
@@ -0,0 +1,217 @@
+import { join } from 'upath';
+import _fs from 'fs-extra';
+import { exec as _exec } from 'child_process';
+import Git from 'simple-git/promise';
+import { platform as _platform } from '../../platform';
+import { updateArtifacts } from '.';
+import * as _datasource from '../../datasource/docker';
+import { mocked } from '../../../test/util';
+import { envMock, mockExecAll } from '../../../test/execUtil';
+import * as _env from '../../util/exec/env';
+import { setExecConfig } from '../../util/exec';
+import { BinarySource } from '../../util/exec/common';
+
+jest.mock('fs-extra');
+jest.mock('child_process');
+jest.mock('../../util/exec/env');
+jest.mock('../../platform');
+jest.mock('../../datasource/docker');
+
+const fs: jest.Mocked<typeof _fs> = _fs as any;
+const exec: jest.Mock<typeof _exec> = _exec as any;
+const env = mocked(_env);
+const platform = mocked(_platform);
+const datasource = mocked(_datasource);
+
+const config = {
+  localDir: join('/tmp/github/some/repo'),
+};
+
+describe('.updateArtifacts()', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+    env.getChildProcessEnv.mockReturnValue(envMock.basic);
+    setExecConfig(config);
+
+    datasource.getPkgReleases.mockResolvedValue({
+      releases: [
+        { version: '1.2.0' },
+        { version: '1.2.1' },
+        { version: '1.2.2' },
+        { version: '1.2.3' },
+        { version: '1.2.4' },
+        { version: '1.2.5' },
+      ],
+    });
+  });
+  it('returns null if no Podfile.lock found', async () => {
+    const execSnapshots = mockExecAll(exec);
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: ['foo'],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('returns null if no updatedDeps were provided', async () => {
+    const execSnapshots = mockExecAll(exec);
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: [],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('returns null for invalid local directory', async () => {
+    const execSnapshots = mockExecAll(exec);
+    const noLocalDirConfig = {
+      localDir: undefined,
+    };
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: ['foo'],
+        newPackageFileContent: '',
+        config: noLocalDirConfig,
+      })
+    ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('returns null if updatedDeps is empty', async () => {
+    const execSnapshots = mockExecAll(exec);
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: [],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('returns null if unchanged', async () => {
+    const execSnapshots = mockExecAll(exec);
+    platform.getFile.mockResolvedValueOnce('Current Podfile');
+    platform.getRepoStatus.mockResolvedValueOnce({
+      modified: [],
+    } as Git.StatusResult);
+    fs.readFile.mockResolvedValueOnce('Current Podfile' as any);
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: ['foo'],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('returns updated Podfile', async () => {
+    const execSnapshots = mockExecAll(exec);
+    setExecConfig({ ...config, binarySource: BinarySource.Docker });
+    platform.getFile.mockResolvedValueOnce('Old Podfile');
+    platform.getRepoStatus.mockResolvedValueOnce({
+      modified: ['Podfile.lock'],
+    } as Git.StatusResult);
+    fs.readFile.mockResolvedValueOnce('New Podfile' as any);
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: ['foo'],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('catches write error', async () => {
+    const execSnapshots = mockExecAll(exec);
+    platform.getFile.mockResolvedValueOnce('Current Podfile');
+    fs.outputFile.mockImplementationOnce(() => {
+      throw new Error('not found');
+    });
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: ['foo'],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('returns pod exec error', async () => {
+    const execSnapshots = mockExecAll(exec, new Error('exec exception'));
+    platform.getFile.mockResolvedValueOnce('Old Podfile.lock');
+    fs.outputFile.mockResolvedValueOnce(null as never);
+    fs.readFile.mockResolvedValueOnce('Old Podfile.lock' as any);
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Podfile',
+        updatedDeps: ['foo'],
+        newPackageFileContent: '',
+        config,
+      })
+    ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('dynamically selects Docker image tag', async () => {
+    const execSnapshots = mockExecAll(exec);
+
+    setExecConfig({
+      ...config,
+      binarySource: 'docker',
+      dockerUser: 'ubuntu',
+    });
+
+    platform.getFile.mockResolvedValueOnce('COCOAPODS: 1.2.4');
+
+    fs.readFile.mockResolvedValueOnce('New Podfile' as any);
+
+    platform.getRepoStatus.mockResolvedValueOnce({
+      modified: ['Podfile.lock'],
+    } as Git.StatusResult);
+
+    await updateArtifacts({
+      packageFileName: 'Podfile',
+      updatedDeps: ['foo'],
+      newPackageFileContent: '',
+      config,
+    });
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('falls back to the `latest` Docker image tag', async () => {
+    const execSnapshots = mockExecAll(exec);
+
+    setExecConfig({
+      ...config,
+      binarySource: 'docker',
+      dockerUser: 'ubuntu',
+    });
+
+    platform.getFile.mockResolvedValueOnce('COCOAPODS: 1.2.4');
+    datasource.getPkgReleases.mockResolvedValueOnce({
+      releases: [],
+    });
+
+    fs.readFile.mockResolvedValueOnce('New Podfile' as any);
+
+    platform.getRepoStatus.mockResolvedValueOnce({
+      modified: ['Podfile.lock'],
+    } as Git.StatusResult);
+
+    await updateArtifacts({
+      packageFileName: 'Podfile',
+      updatedDeps: ['foo'],
+      newPackageFileContent: '',
+      config,
+    });
+    expect(execSnapshots).toMatchSnapshot();
+  });
+});
diff --git a/lib/manager/cocoapods/artifacts.ts b/lib/manager/cocoapods/artifacts.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cfab5e393751f362ec2a8fd83c367bca01363e0f
--- /dev/null
+++ b/lib/manager/cocoapods/artifacts.ts
@@ -0,0 +1,89 @@
+import { platform } from '../../platform';
+import { exec, ExecOptions } from '../../util/exec';
+import { logger } from '../../logger';
+import { UpdateArtifact, UpdateArtifactsResult } from '../common';
+import {
+  getSiblingFileName,
+  readLocalFile,
+  writeLocalFile,
+} from '../../util/fs';
+
+export async function updateArtifacts({
+  packageFileName,
+  updatedDeps,
+  newPackageFileContent,
+  config,
+}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
+  logger.debug(`cocoapods.getArtifacts(${packageFileName})`);
+
+  if (updatedDeps.length < 1) {
+    logger.debug('CocoaPods: empty update - returning null');
+    return null;
+  }
+
+  const lockFileName = getSiblingFileName(packageFileName, 'Podfile.lock');
+
+  try {
+    await writeLocalFile(packageFileName, newPackageFileContent);
+  } catch (err) {
+    logger.warn({ err }, 'Podfile could not be written');
+    return [
+      {
+        artifactError: {
+          lockFile: lockFileName,
+          stderr: err.message,
+        },
+      },
+    ];
+  }
+
+  const existingLockFileContent = await platform.getFile(lockFileName);
+  if (!existingLockFileContent) {
+    logger.debug(`Lockfile not found: ${lockFileName}`);
+    return null;
+  }
+
+  const match = new RegExp(/^COCOAPODS: (?<cocoapodsVersion>.*)$/m).exec(
+    existingLockFileContent
+  );
+  const tagConstraint =
+    match && match.groups ? match.groups.cocoapodsVersion : null;
+
+  const cmd = 'pod install';
+  const execOptions: ExecOptions = {
+    cwdFile: packageFileName,
+    docker: {
+      image: 'renovate/cocoapods',
+      tagScheme: 'ruby',
+      tagConstraint,
+    },
+  };
+
+  try {
+    await exec(cmd, execOptions);
+  } catch (err) {
+    return [
+      {
+        artifactError: {
+          lockFile: lockFileName,
+          stderr: err.stderr || err.stdout || err.message,
+        },
+      },
+    ];
+  }
+
+  const status = await platform.getRepoStatus();
+  if (!status.modified.includes(lockFileName)) {
+    return null;
+  }
+  logger.debug('Returning updated Gemfile.lock');
+  const lockFileContent = await readLocalFile(lockFileName);
+  return [
+    {
+      file: {
+        name: lockFileName,
+        contents: lockFileContent,
+      },
+    },
+  ];
+}
diff --git a/lib/manager/cocoapods/extract.spec.ts b/lib/manager/cocoapods/extract.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd7c2b3005cd154698f83f24c5f0fb9c18c65a76
--- /dev/null
+++ b/lib/manager/cocoapods/extract.spec.ts
@@ -0,0 +1,25 @@
+import fs from 'fs-extra';
+import path from 'path';
+import { extractPackageFile } from '.';
+
+const simplePodfile = fs.readFileSync(
+  path.resolve(__dirname, './__fixtures__/Podfile.simple'),
+  'utf-8'
+);
+
+const complexPodfile = fs.readFileSync(
+  path.resolve(__dirname, './__fixtures__/Podfile.complex'),
+  'utf-8'
+);
+
+describe('lib/manager/cocoapods/extract', () => {
+  describe('extractPackageFile()', () => {
+    it('extracts all dependencies', () => {
+      const simpleResult = extractPackageFile(simplePodfile).deps;
+      expect(simpleResult).toMatchSnapshot();
+
+      const complexResult = extractPackageFile(complexPodfile).deps;
+      expect(complexResult).toMatchSnapshot();
+    });
+  });
+});
diff --git a/lib/manager/cocoapods/extract.ts b/lib/manager/cocoapods/extract.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0e66c59a53eddb6a8ab1fb2552b12722519eb2ae
--- /dev/null
+++ b/lib/manager/cocoapods/extract.ts
@@ -0,0 +1,133 @@
+import { logger } from '../../logger';
+import { PackageDependency, PackageFile } from '../common';
+import * as datasourcePod from '../../datasource/pod';
+
+const regexMappings = [
+  /^\s*pod\s+(['"])(?<spec>[^'"/]+)(\/(?<subspec>[^'"]+))?\1/,
+  /^\s*pod\s+(['"])[^'"]+\1\s*,\s*(['"])(?<currentValue>[^'"]+)\2\s*$/,
+  /,\s*:git\s*=>\s*(['"])(?<git>[^'"]+)\1/,
+  /,\s*:tag\s*=>\s*(['"])(?<tag>[^'"]+)\1/,
+  /,\s*:path\s*=>\s*(['"])(?<path>[^'"]+)\1/,
+  /^\s*source\s*(['"])(?<source>[^'"]+)\1/,
+];
+
+export interface ParsedLine {
+  depName?: string;
+  groupName?: string;
+  spec?: string;
+  subspec?: string;
+  currentValue?: string;
+  git?: string;
+  tag?: string;
+  path?: string;
+  source?: string;
+}
+
+export function parseLine(line: string): ParsedLine {
+  const result: ParsedLine = {};
+  for (const regex of Object.values(regexMappings)) {
+    const match = regex.exec(line.replace(/#.*$/, ''));
+    if (match && match.groups) {
+      Object.assign(result, match.groups);
+    }
+  }
+
+  if (result.spec) {
+    const depName = result.subspec
+      ? `${result.spec}/${result.subspec}`
+      : result.spec;
+    const groupName = result.spec;
+    if (depName) result.depName = depName;
+    if (groupName) result.groupName = groupName;
+    delete result.spec;
+    delete result.subspec;
+  }
+
+  return result;
+}
+
+export function gitDep(parsedLine: ParsedLine): PackageDependency | null {
+  const { depName, git, tag } = parsedLine;
+  if (git && git.startsWith('https://github.com/')) {
+    const githubMatch = /https:\/\/github\.com\/(?<account>[^/]+)\/(?<repo>[^/]+)/.exec(
+      git
+    );
+    const { account, repo } = (githubMatch && githubMatch.groups) || {};
+    if (account && repo) {
+      return {
+        datasource: 'github-tags',
+        depName,
+        lookupName: `${account}/${repo.replace(/\.git$/, '')}`,
+        currentValue: tag,
+      };
+    }
+  }
+
+  return null; // TODO: gitlab or gitTags datasources?
+}
+
+export function extractPackageFile(content: string): PackageFile | null {
+  logger.trace('cocoapods.extractPackageFile()');
+  const deps: PackageDependency[] = [];
+  const lines: string[] = content.split('\n');
+
+  const registryUrls: string[] = [];
+
+  for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
+    const line = lines[lineNumber];
+    const parsedLine = parseLine(line);
+    const {
+      depName,
+      groupName,
+      currentValue,
+      git,
+      tag,
+      path,
+      source,
+    }: ParsedLine = parsedLine;
+
+    if (source) {
+      registryUrls.push(source.replace(/\/*$/, ''));
+    }
+
+    if (depName) {
+      const managerData = { lineNumber };
+      let dep: PackageDependency = {
+        depName,
+        groupName,
+        skipReason: 'unknown-version',
+      };
+
+      if (currentValue) {
+        dep = {
+          depName,
+          groupName,
+          datasource: datasourcePod.id,
+          currentValue,
+          managerData,
+          registryUrls,
+        };
+      } else if (git) {
+        if (tag) {
+          dep = { ...gitDep(parsedLine), managerData };
+        } else {
+          dep = {
+            depName,
+            groupName,
+            skipReason: 'git-dependency',
+          };
+        }
+      } else if (path) {
+        dep = {
+          depName,
+          groupName,
+          skipReason: 'path-dependency',
+        };
+      }
+
+      deps.push(dep);
+    }
+  }
+
+  return deps.length ? { deps } : null;
+}
diff --git a/lib/manager/cocoapods/index.ts b/lib/manager/cocoapods/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f39f897123271ce71a99f0193adba91ec784bbb4
--- /dev/null
+++ b/lib/manager/cocoapods/index.ts
@@ -0,0 +1,11 @@
+import * as rubyVersioning from '../../versioning/ruby';
+
+export { extractPackageFile } from './extract';
+export { updateDependency } from './update';
+export { updateArtifacts } from './artifacts';
+
+export const defaultConfig = {
+  enabled: false,
+  fileMatch: ['(^|/)Podfile$'],
+  versioning: rubyVersioning.id,
+};
diff --git a/lib/manager/cocoapods/readme.md b/lib/manager/cocoapods/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..d57162968f606a90750b6863c17efe5146b9b70c
--- /dev/null
+++ b/lib/manager/cocoapods/readme.md
@@ -0,0 +1,9 @@
+The `cocoapods` manager extracts dependencies with`datasource` type `pod`. It is currently in beta so disabled by default. To opt-in to the beta, add the following to your configuration:
+
+```json
+{
+  "cocoapods": {
+    "enabled": true
+  }
+}
+```
diff --git a/lib/manager/cocoapods/update.spec.ts b/lib/manager/cocoapods/update.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee73d947ceb93195fc5479a37acf8a8cb20c4baf
--- /dev/null
+++ b/lib/manager/cocoapods/update.spec.ts
@@ -0,0 +1,45 @@
+import fs from 'fs-extra';
+import path from 'path';
+import { updateDependency } from '.';
+
+const fileContent = fs.readFileSync(
+  path.resolve(__dirname, './__fixtures__/Podfile.simple'),
+  'utf-8'
+);
+
+describe('lib/manager/cocoapods/update', () => {
+  describe('updateDependency', () => {
+    it('replaces existing value', () => {
+      const upgrade = {
+        depName: 'b',
+        managerData: { lineNumber: 4 },
+        currentValue: '1.2.3',
+        newValue: '2.0.0',
+      };
+      const res = updateDependency({ fileContent, upgrade });
+      expect(res).not.toEqual(fileContent);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+    });
+    it('returns same content', () => {
+      const upgrade = {
+        depName: 'b',
+        managerData: { lineNumber: 4 },
+        currentValue: '1.2.3',
+        newValue: '1.2.3',
+      };
+      const res = updateDependency({ fileContent, upgrade });
+      expect(res).toEqual(fileContent);
+      expect(res).toBe(fileContent);
+    });
+    it('returns null', () => {
+      const upgrade = {
+        depName: 'b',
+        managerData: { lineNumber: 0 },
+        currentValue: '1.2.3',
+        newValue: '2.0.0',
+      };
+      const res = updateDependency({ fileContent, upgrade });
+      expect(res).toBeNull();
+    });
+  });
+});
diff --git a/lib/manager/cocoapods/update.ts b/lib/manager/cocoapods/update.ts
new file mode 100644
index 0000000000000000000000000000000000000000..afb0b31da539b30433fab487d24032bfc3facb9f
--- /dev/null
+++ b/lib/manager/cocoapods/update.ts
@@ -0,0 +1,39 @@
+import { logger } from '../../logger';
+import { UpdateDependencyConfig } from '../common';
+import { parseLine } from './extract';
+
+function lineContainsDep(line: string, dep: string): boolean {
+  const { depName } = parseLine(line);
+  return dep === depName;
+}
+
+export function updateDependency({
+  fileContent,
+  upgrade,
+}: UpdateDependencyConfig): string | null {
+  const { currentValue, managerData, depName, newValue } = upgrade;
+
+  // istanbul ignore if
+  if (!currentValue || !managerData || !depName) {
+    logger.warn('Cocoapods: invalid upgrade object');
+    return null;
+  }
+
+  logger.debug(`cocoapods.updateDependency: ${newValue}`);
+
+  const lines = fileContent.split('\n');
+  const lineToChange = lines[managerData.lineNumber];
+
+  if (!lineContainsDep(lineToChange, depName)) return null;
+
+  const regex = new RegExp(`(['"])${currentValue.replace('.', '\\.')}\\1`);
+  const newLine = lineToChange.replace(regex, `$1${newValue}$1`);
+
+  if (newLine === lineToChange) {
+    logger.debug('No changes necessary');
+    return fileContent;
+  }
+
+  lines[managerData.lineNumber] = newLine;
+  return lines.join('\n');
+}