diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index ee5b0e4924e36a97b0d65a9cf797562b59abeaac..8855775f9d5ed11c707530dd3f62f8eebad970b2 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -999,6 +999,27 @@ const options = [
     },
     mergeable: true,
   },
+  {
+    name: 'ruby',
+    releaseStatus: 'unpublished',
+    description: 'Configuration object for ruby language',
+    stage: 'package',
+    type: 'json',
+    default: {},
+    mergeable: true,
+    cli: false,
+  },
+  {
+    name: 'bundler',
+    releaseStatus: 'unpublished',
+    description: 'Configuration object for bundler Gemfiles',
+    stage: 'package',
+    type: 'json',
+    default: {
+      fileMatch: [],
+    },
+    mergeable: true,
+  },
   {
     name: 'terraform',
     description: 'Configuration object for Terraform module renovation',
diff --git a/lib/manager/bundler/artifacts.js b/lib/manager/bundler/artifacts.js
new file mode 100644
index 0000000000000000000000000000000000000000..5582aee4e836cbb6dfa82adbc4034f9efbc94b76
--- /dev/null
+++ b/lib/manager/bundler/artifacts.js
@@ -0,0 +1,19 @@
+module.exports = {
+  getArtifacts,
+};
+
+/*
+ *  The getArtifacts() function is optional and necessary only if it is necessary to update "artifacts"
+ *  after updating package files. Artifacts are files such as lock files or checksum files.
+ *  Usually this will require running a child process command to produce an update.
+ */
+
+async function getArtifacts(
+  packageFileName,
+  updatedDeps,
+  newPackageFileContent,
+  config
+) {
+  await logger.debug({ config }, `composer.getArtifacts(${packageFileName})`);
+  return null;
+}
diff --git a/lib/manager/bundler/extract.js b/lib/manager/bundler/extract.js
new file mode 100644
index 0000000000000000000000000000000000000000..7959622696e95948187a220704bae981e30d80bb
--- /dev/null
+++ b/lib/manager/bundler/extract.js
@@ -0,0 +1,18 @@
+module.exports = {
+  extractPackageFile,
+};
+
+/*
+ * The extractPackageFile() function is mandatory unless extractAllPackageFiles() is used instead.
+ *
+ * Use extractPackageFile() if it is OK for Renovate to parse/extract package files in parallel independently.
+ *
+ * Here are examples of when extractAllPackageFiles has been necessary to be used instead:
+ *  - for npm/yarn/lerna, "monorepos" can have links between package files and logic requiring us to selectively ignore "internal" dependencies within the same repository
+ *  - for gradle, we use a third party CLI tool to extract all dependencies at once and so it should not be called independently on each package file separately
+ */
+
+function extractPackageFile(content, fileName) {
+  logger.trace(`bundler.extractPackageFile(${fileName})`);
+  return null;
+}
diff --git a/lib/manager/bundler/index.js b/lib/manager/bundler/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..5d5954b56115c326f2601574f22f0b1fe643a0af
--- /dev/null
+++ b/lib/manager/bundler/index.js
@@ -0,0 +1,19 @@
+const { extractPackageFile } = require('./extract');
+const { updateDependency } = require('./update');
+const { getArtifacts } = require('./artifacts');
+const { getRangeStrategy } = require('./range');
+
+const language = 'ruby';
+
+/*
+ * Each of the below functions contain some explanations within their own files.
+ * The best way to understand them in more detail is to look at the existing managers and find one that matches most closely what you need to do.
+ */
+
+module.exports = {
+  extractPackageFile, // Mandatory unless extractAllPackageFiles is used instead
+  getArtifacts, // Optional
+  getRangeStrategy, // Optional
+  language, // Optional
+  updateDependency, // Mandatory
+};
diff --git a/lib/manager/bundler/range.js b/lib/manager/bundler/range.js
new file mode 100644
index 0000000000000000000000000000000000000000..382f15b7fafa7f5487ea2670f43428a3b7a7244c
--- /dev/null
+++ b/lib/manager/bundler/range.js
@@ -0,0 +1,21 @@
+module.exports = {
+  getRangeStrategy,
+};
+
+/*
+ * The getRangeStrategy() function is optional and can be removed if not applicable.
+ * It is used when the user configures rangeStrategy=auto.
+ *
+ * For example in npm, when rangeStrategy is auto we:
+ *  - Always pin "devDependencies"
+ *  - Pin "dependencies" only if we detect that it's probably an app not a library
+ *  - Always widen "peerDependencies"
+ *
+ * If this function is not present then the default 'replace' value will be used.
+ *
+ */
+
+function getRangeStrategy(config) {
+  logger.debug({ config });
+  return 'replace';
+}
diff --git a/lib/manager/bundler/readme.md b/lib/manager/bundler/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..9727941975c5edabe9f15486e163db6c1be68d28
--- /dev/null
+++ b/lib/manager/bundler/readme.md
@@ -0,0 +1,107 @@
+## Overview
+
+#### Name of package manager
+
+---
+
+#### What language does this support?
+
+---
+
+#### Does that language have other (competing?) package managers?
+
+## Package File Detection
+
+#### What type of package files and names does it use?
+
+---
+
+#### What [fileMatch](https://renovatebot.com/docs/configuration-options/#filematch) pattern(s) should be used?
+
+---
+
+#### Is it likely that many users would need to extend this pattern for custom file names?
+
+---
+
+#### Is the fileMatch pattern likely to get many "false hits" for files that have nothing to do with package management?
+
+## Parsing and Extraction
+
+#### Can package files have "local" links to each other that need to be resolved?
+
+#### Is there reason why package files need to be parsed together (in serial) instead of independently?
+
+---
+
+#### What format/syntax is the package file in? e.g. JSON, TOML, custom?
+
+---
+
+#### How do you suggest parsing the file? Using an off-the-shelf parser, using regex, or can it be custom-parsed line by line?
+
+---
+
+#### Does the package file structure distinguish between different "types" of dependencies? e.g. production dependencies, dev dependencies, etc?
+
+No
+
+---
+
+#### List all the sources/syntaxes of dependencies that can be extracted:
+
+---
+
+#### Describe which types of dependencies above are supported and which will be implemented in future:
+
+## Versioning
+
+#### What versioning scheme do the package files use?
+
+---
+
+#### Does this versioning scheme support range constraints, e.g. `^1.0.0` or `1.x`?
+
+---
+
+#### Is this package manager used for applications, libraries, or both? If both, is there a way to tell which is which?
+
+---
+
+#### If ranges are supported, are there any cases when Renovate should pin ranges to exact versions if rangeStrategy=auto?
+
+## Lookup
+
+#### Is a new datasource required? Provide details
+
+---
+
+#### Will users need the capability to specify a custom host/registry to look up? Can it be found within the package files, or within other files inside the repository, or would it require Renovate configuration?
+
+---
+
+#### Do the package files contain any "constraints" on the parent language (e.g. supports only v3.x of Python) or platform (Linux, Windows, etc) that should be used in the lookup procedure?
+
+---
+
+#### Will users need the ability to configure language or other constraints using Renovate config?
+
+## Artifacts
+
+#### Are lock files or checksum files used? Mandatory?
+
+---
+
+#### If so, what tool and exact commands should be used if updating 1 or more package versions in a dependency file?
+
+---
+
+#### If applicable, describe how the tool maintains a cache and if it can be controlled via CLI or env? Do you recommend the cache be kept or disabled/ignored?
+
+---
+
+#### If applicable, what command should be used to generate a lock file from scratch if you already have a package file? This will be used for "lock file maintenance".
+
+## Other
+
+#### Is there anything else to know about this package manager?
diff --git a/lib/manager/bundler/update.js b/lib/manager/bundler/update.js
new file mode 100644
index 0000000000000000000000000000000000000000..89178929fbfd99323baa0b25c0d4d0d1b9c7902f
--- /dev/null
+++ b/lib/manager/bundler/update.js
@@ -0,0 +1,15 @@
+module.exports = {
+  updateDependency,
+};
+
+/*
+ * The updateDependency() function is mandatory, and is used for updating one dependency at a time.
+ * It returns the currentFileContent if no changes are necessary (e.g. because the existing branch/PR is up to date),
+ * or with new content if changes are necessary.
+ */
+
+function updateDependency(currentFileContent, upgrade) {
+  logger.debug({ config: upgrade }, 'bundler.updateDependency()');
+  // TODO
+  return currentFileContent;
+}
diff --git a/lib/manager/index.js b/lib/manager/index.js
index cebad074bec84b1286b4d98cf88e33a0f53215b4..dd630c15fa9f35b487d9936018e485d380175607 100644
--- a/lib/manager/index.js
+++ b/lib/manager/index.js
@@ -10,6 +10,7 @@ const managerList = [
   'gomod',
   'kubernetes',
   'meteor',
+  'bundler',
   'npm',
   'nvm',
   'pip_requirements',
@@ -29,6 +30,7 @@ const languageList = [
   'docker',
   'golang',
   'js',
+  'ruby',
   'node',
   'php',
   'python',
diff --git a/test/manager/__snapshots__/manager-docs.spec.js.snap b/test/manager/__snapshots__/manager-docs.spec.js.snap
index 1fb82070ede431673f625d9a590aad15323de4fc..837f06458ff1e26faa9b73b0d46bb464d9901e56 100644
--- a/test/manager/__snapshots__/manager-docs.spec.js.snap
+++ b/test/manager/__snapshots__/manager-docs.spec.js.snap
@@ -4,6 +4,7 @@ exports[`manager readmes has same questions for all managers 1`] = `
 Array [
   "bazel",
   "buildkite",
+  "bundler",
   "cargo",
   "circleci",
   "composer",
@@ -87,3 +88,35 @@ Array [
   "Is there anything else to know about this package manager?",
 ]
 `;
+
+exports[`manager readmes has same questions for all managers 4`] = `
+Array [
+  "Name of package manager",
+  "What language does this support?",
+  "Does that language have other (competing?) package managers?",
+  "What type of package files and names does it use?",
+  "What [fileMatch](https://renovatebot.com/docs/configuration-options/#filematch) pattern(s) should be used?",
+  "Is it likely that many users would need to extend this pattern for custom file names?",
+  "Is the fileMatch pattern likely to get many \\"false hits\\" for files that have nothing to do with package management?",
+  "Can package files have \\"local\\" links to each other that need to be resolved?",
+  "Is there reason why package files need to be parsed together (in serial) instead of independently?",
+  "What format/syntax is the package file in? e.g. JSON, TOML, custom?",
+  "How do you suggest parsing the file? Using an off-the-shelf parser, using regex, or can it be custom-parsed line by line?",
+  "Does the package file structure distinguish between different \\"types\\" of dependencies? e.g. production dependencies, dev dependencies, etc?",
+  "List all the sources/syntaxes of dependencies that can be extracted:",
+  "Describe which types of dependencies above are supported and which will be implemented in future:",
+  "What versioning scheme do the package files use?",
+  "Does this versioning scheme support range constraints, e.g. \`^1.0.0\` or \`1.x\`?",
+  "Is this package manager used for applications, libraries, or both? If both, is there a way to tell which is which?",
+  "If ranges are supported, are there any cases when Renovate should pin ranges to exact versions if rangeStrategy=auto?",
+  "Is a new datasource required? Provide details",
+  "Will users need the capability to specify a custom host/registry to look up? Can it be found within the package files, or within other files inside the repository, or would it require Renovate configuration?",
+  "Do the package files contain any \\"constraints\\" on the parent language (e.g. supports only v3.x of Python) or platform (Linux, Windows, etc) that should be used in the lookup procedure?",
+  "Will users need the ability to configure language or other constraints using Renovate config?",
+  "Are lock files or checksum files used? Mandatory?",
+  "If so, what tool and exact commands should be used if updating 1 or more package versions in a dependency file?",
+  "If applicable, describe how the tool maintains a cache and if it can be controlled via CLI or env? Do you recommend the cache be kept or disabled/ignored?",
+  "If applicable, what command should be used to generate a lock file from scratch if you already have a package file? This will be used for \\"lock file maintenance\\".",
+  "Is there anything else to know about this package manager?",
+]
+`;
diff --git a/test/manager/newmanager/artifacts.spec.js b/test/manager/newmanager/artifacts.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..fe2918907f83c7db42dd15c571e875a6445929f0
--- /dev/null
+++ b/test/manager/newmanager/artifacts.spec.js
@@ -0,0 +1,13 @@
+const cargo = require('../../../lib/manager/bundler/artifacts');
+
+let config;
+
+describe('bundler.getArtifacts()', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+    config = {};
+  });
+  it('returns null by default', async () => {
+    expect(await cargo.getArtifacts('', [], '', config)).toBeNull();
+  });
+});
diff --git a/test/manager/newmanager/extract.spec.js b/test/manager/newmanager/extract.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..d987c82c38a2540fd4637a117986d21845093ba9
--- /dev/null
+++ b/test/manager/newmanager/extract.spec.js
@@ -0,0 +1,13 @@
+const { extractPackageFile } = require('../../../lib/manager/bundler/extract');
+
+describe('lib/manager/bundler/extract', () => {
+  describe('extractPackageFile()', () => {
+    let config;
+    beforeEach(() => {
+      config = {};
+    });
+    it('returns null for empty', () => {
+      expect(extractPackageFile('nothing here', config)).toBeNull();
+    });
+  });
+});
diff --git a/test/manager/newmanager/range.spec.js b/test/manager/newmanager/range.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9885a7d23f6d497bccf36c61ab6eddb0c8c91bdb
--- /dev/null
+++ b/test/manager/newmanager/range.spec.js
@@ -0,0 +1,13 @@
+const { getRangeStrategy } = require('../../../lib/manager/bundler');
+
+describe('lib/manager/bundler/range', () => {
+  describe('getRangeStrategy()', () => {
+    let config;
+    beforeEach(() => {
+      config = {};
+    });
+    it('always returns replace', () => {
+      expect(getRangeStrategy(config)).toEqual('replace');
+    });
+  });
+});
diff --git a/test/manager/newmanager/update.spec.js b/test/manager/newmanager/update.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9ab00c8d7b2c5f3da1f89fe367aa8f9ce19187b2
--- /dev/null
+++ b/test/manager/newmanager/update.spec.js
@@ -0,0 +1,13 @@
+const { updateDependency } = require('../../../lib/manager/bundler/update');
+
+describe('lib/manager/bundler/update', () => {
+  describe('updateDependency()', () => {
+    let config;
+    beforeEach(() => {
+      config = {};
+    });
+    it('returns same', () => {
+      expect(updateDependency('abc', config)).toEqual('abc');
+    });
+  });
+});
diff --git a/test/workers/repository/extract/__snapshots__/index.spec.js.snap b/test/workers/repository/extract/__snapshots__/index.spec.js.snap
index f958872656688a92e03d4e3da827cd3408b72eb4..b45f5f37dbb0ecc88708ab7b8b9e51228b9f3f23 100644
--- a/test/workers/repository/extract/__snapshots__/index.spec.js.snap
+++ b/test/workers/repository/extract/__snapshots__/index.spec.js.snap
@@ -8,6 +8,9 @@ Object {
   "buildkite": Array [
     Object {},
   ],
+  "bundler": Array [
+    Object {},
+  ],
   "cargo": Array [
     Object {},
   ],