diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 970493195146df7566adbe61067e592d9a7b6309..ac52d11e3fc88e338601bd52801ae33fe3a37172 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -727,6 +727,7 @@ const options = [
       enabled: true,
       groupName: null,
       schedule: [],
+      masterIssueApproval: false,
       commitMessageSuffix: '[SECURITY]',
     },
     cli: false,
diff --git a/lib/config/validation.js b/lib/config/validation.js
index 19362acc8eaf7211a48b73e62902a2b6d9ebc40b..35587d9e0901d485a0b82186f46e87d5b55cf799 100644
--- a/lib/config/validation.js
+++ b/lib/config/validation.js
@@ -44,6 +44,10 @@ async function validateConfig(config, isPreset, parentPath) {
       'vulnerabilityAlertsOnly',
       'copyLocalLibs', // deprecated - functinoality is now enabled by default
       'prBody', // deprecated
+      'masterIssue',
+      'masterIssueTitle',
+      'masterIssueApproval',
+      'masterIssueAutoclose',
     ];
     return ignoredNodes.includes(key);
   }
diff --git a/lib/workers/branch/index.js b/lib/workers/branch/index.js
index 91da9b59058b1e2879e0c72ecee4d5b7a164a01e..6e7183733cc889e8ca45dfe58e06b94b20cb421a 100644
--- a/lib/workers/branch/index.js
+++ b/lib/workers/branch/index.js
@@ -34,6 +34,20 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
   const branchExists = await platform.branchExists(config.branchName);
   const branchPr = await platform.getBranchPr(config.branchName);
   logger.debug(`branchExists=${branchExists}`);
+  const masterIssueCheck = (config.masterIssueChecks || {})[config.branchName];
+  // istanbul ignore if
+  if (masterIssueCheck) {
+    logger.info('Branch has been checked in master issue: ' + masterIssueCheck);
+  }
+  // istanbul ignore if
+  if (!branchExists && config.masterIssueApproval) {
+    if (masterIssueCheck) {
+      logger.info(`Branch ${config.branchName} is approved for creation`);
+    } else {
+      logger.info(`Branch ${config.branchName} needs approval`);
+      return 'needs-approval';
+    }
+  }
   try {
     logger.debug(
       `Branch has ${dependencies ? dependencies.length : 0} upgrade(s)`
@@ -41,7 +55,7 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
 
     // Check if branch already existed
     const existingPr = branchPr ? undefined : await prAlreadyExisted(config);
-    if (existingPr) {
+    if (existingPr && !masterIssueCheck) {
       logger.debug(
         { prTitle: config.prTitle },
         'Closed PR already exists. Skipping branch.'
@@ -80,7 +94,7 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
       }
       return 'already-existed';
     }
-    if (!branchExists && prHourlyLimitReached) {
+    if (!branchExists && prHourlyLimitReached && !masterIssueCheck) {
       logger.info('Reached PR creation limit - skipping branch creation');
       return 'pr-hourly-limit-reached';
     }
@@ -117,7 +131,7 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
 
     // Check schedule
     config.isScheduledNow = isScheduledNow(config);
-    if (!config.isScheduledNow) {
+    if (!config.isScheduledNow && !masterIssueCheck) {
       if (!branchExists) {
         logger.info('Skipping branch creation as not within schedule');
         return 'not-scheduled';
@@ -148,8 +162,13 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
       );
       return 'pending';
     }
-
-    Object.assign(config, await getParentBranch(config));
+    // istanbul ignore if
+    if (masterIssueCheck === 'rebase') {
+      logger.info('Manual rebase requested via master issue');
+      delete config.parentBranch;
+    } else {
+      Object.assign(config, await getParentBranch(config));
+    }
     logger.debug(`Using parentBranch: ${config.parentBranch}`);
     const res = await getUpdatedPackageFiles(config);
     // istanbul ignore if
diff --git a/lib/workers/repository/index.js b/lib/workers/repository/index.js
index 95b8322080a9890deebf785a005eaaf5b30b87e6..ac8e98df42fb125c631b8d1c4151dbdfb487cbcc 100644
--- a/lib/workers/repository/index.js
+++ b/lib/workers/repository/index.js
@@ -8,6 +8,7 @@ const { handleError } = require('./error');
 const { processResult } = require('./result');
 const { processRepo } = require('./process');
 const { finaliseRepo } = require('./finalise');
+const { ensureMasterIssue } = require('./master-issue');
 
 module.exports = {
   renovateRepository,
@@ -34,6 +35,7 @@ async function renovateRepository(repoConfig) {
       config
     );
     await ensureOnboardingPr(config, packageFiles, branches);
+    await ensureMasterIssue(config, branches);
     await finaliseRepo(config, branchList);
     return processResult(config, res);
   } catch (err) /* istanbul ignore next */ {
diff --git a/lib/workers/repository/master-issue.js b/lib/workers/repository/master-issue.js
new file mode 100644
index 0000000000000000000000000000000000000000..936c3ad41b343b355de3a2976b08e6db46e7542f
--- /dev/null
+++ b/lib/workers/repository/master-issue.js
@@ -0,0 +1,127 @@
+module.exports = {
+  ensureMasterIssue,
+};
+
+// istanbul ignore next
+function getListItem(branch, type) {
+  let item = ' - [ ] ';
+  item += `<!-- ${type}-branch=${branch.branchName} -->`;
+  if (branch.prNo) {
+    item += `[${branch.prTitle}](../pull/${branch.prNo})`;
+  } else {
+    item += branch.prTitle;
+  }
+  if (branch.upgrades.length < 2) {
+    return item + '\n';
+  }
+  return (
+    item +
+    ' (' +
+    branch.upgrades.map(upgrade => '`' + upgrade.depName + '`').join(', ') +
+    ')\n'
+  );
+}
+
+// istanbul ignore next
+async function ensureMasterIssue(config, branches) {
+  if (!(config.masterIssue || config.masterIssueApproval)) {
+    return;
+  }
+  if (
+    !branches.length ||
+    branches.every(branch => branch.res === 'automerged')
+  ) {
+    if (config.masterIssueAutoclose) {
+      await platform.ensureIssueClosing(config.masterIssueTitle);
+      return;
+    }
+    await platform.ensureIssue(
+      config.masterIssueTitle,
+      'This repository is up-to-date and has no outstanding updates open or pending.'
+    );
+    return;
+  }
+  let issueBody =
+    'This issue contains a list of Renovate updates and their statuses.\n\n';
+  const pendingApprovals = branches.filter(
+    branch => branch.res === 'needs-approval'
+  );
+  if (pendingApprovals.length) {
+    issueBody += '## Pending Approval\n\n';
+    issueBody +=
+      'These PRs will be created by Renovate only once you click their checkbox below.\n\n';
+    for (const branch of pendingApprovals) {
+      issueBody += getListItem(branch, 'approve');
+    }
+    issueBody += '\n';
+  }
+  const awaitingSchedule = branches.filter(
+    branch => branch.res === 'not-scheduled'
+  );
+  if (awaitingSchedule.length) {
+    issueBody += '## Awaiting Schedule\n\n';
+    issueBody +=
+      'These updates are awaiting their schedule. Click on a checkbox to ignore the schedule.\n';
+    for (const branch of awaitingSchedule) {
+      issueBody += getListItem(branch, 'unschedule');
+    }
+    issueBody += '\n';
+  }
+  const rateLimited = branches.filter(branch =>
+    branch.res.endsWith('pr-hourly-limit-reached')
+  );
+  if (rateLimited.length) {
+    issueBody += '## Rate Limited\n\n';
+    issueBody +=
+      'These updates are currently rate limited. Click on a checkbox below to override.\n\n';
+    for (const branch of rateLimited) {
+      issueBody += getListItem(branch, 'unlimit');
+    }
+    issueBody += '\n';
+  }
+  const errorList = branches.filter(branch => branch.res.endsWith('error'));
+  if (errorList.length) {
+    issueBody += '## Errored\n\n';
+    issueBody +=
+      'These updates encountered an error and will be retried. Click a checkbox below to force a retry now.\n\n';
+    for (const branch of errorList) {
+      issueBody += getListItem(branch, 'retry');
+    }
+    issueBody += '\n';
+  }
+  const otherRes = [
+    'needs-approval',
+    'not-scheduled',
+    'pr-hourly-limit-reached',
+    'already-existed',
+    'error',
+    'automerged',
+  ];
+  const inProgress = branches.filter(branch => !otherRes.includes(branch.res));
+  if (inProgress.length) {
+    issueBody += '## Open\n\n';
+    issueBody +=
+      'These updates have all been created already. Click a checkbox below to force a retry/rebase of any.\n\n';
+    for (const branch of inProgress) {
+      const pr = await platform.getBranchPr(branch.branchName);
+      if (pr) {
+        branch.prNo = pr.number;
+      }
+      issueBody += getListItem(branch, 'rebase');
+    }
+    issueBody += '\n';
+  }
+  const alreadyExisted = branches.filter(branch =>
+    branch.res.endsWith('already-existed')
+  );
+  if (alreadyExisted.length) {
+    issueBody += '## Closed/Ignored\n\n';
+    issueBody +=
+      'These updates were closed unmerged and will not be recreated unless you click a checkbox below.\n\n';
+    for (const branch of alreadyExisted) {
+      issueBody += getListItem(branch, 'recreate');
+    }
+    issueBody += '\n';
+  }
+  await platform.ensureIssue(config.masterIssueTitle, issueBody);
+}
diff --git a/lib/workers/repository/process/index.js b/lib/workers/repository/process/index.js
index 459d2c3d28b870b58a83d026d8f7a16c8eb99f6e..f4eddec6fb34585bb1f4704e35c12ab54a46c02a 100644
--- a/lib/workers/repository/process/index.js
+++ b/lib/workers/repository/process/index.js
@@ -7,6 +7,25 @@ module.exports = {
 
 async function processRepo(config) {
   logger.debug('processRepo()');
+  /* eslint-disable no-param-reassign */
+  config.masterIssueChecks = {};
+  // istanbul ignore if
+  if (config.masterIssue || config.masterIssueApproval) {
+    config.masterIssueTitle =
+      config.masterIssueTitle || 'Update Dependencies (Renovate Bot)';
+    const issue = await platform.findIssue(config.masterIssueTitle);
+    if (issue) {
+      const checkMatch = ' - \\[x\\] <!-- ([a-z]+)-branch=([^\\s]+) -->';
+      const checked = issue.body.match(new RegExp(checkMatch, 'g'));
+      if (checked && checked.length) {
+        checked.forEach(check => {
+          const [, type, branchName] = check.match(new RegExp(checkMatch));
+          config.masterIssueChecks[branchName] = type;
+        });
+        /* eslint-enable no-param-reassign */
+      }
+    }
+  }
   if (config.baseBranches && config.baseBranches.length) {
     logger.info({ baseBranches: config.baseBranches }, 'baseBranches');
     let res;
diff --git a/lib/workers/repository/updates/generate.js b/lib/workers/repository/updates/generate.js
index 79383667b0da0e04ea95bc0933ac636681daa938..70fa54d6e456e99cc88cfcc39f245509be8a3b3a 100644
--- a/lib/workers/repository/updates/generate.js
+++ b/lib/workers/repository/updates/generate.js
@@ -166,6 +166,9 @@ function generateBranchConfig(branchUpgrades) {
   config.reuseLockFiles = config.upgrades.every(
     upgrade => upgrade.updateType !== 'lockFileMaintenance'
   );
+  config.masterIssueApproval = config.upgrades.some(
+    upgrade => upgrade.masterIssueApproval
+  );
   config.automerge = config.upgrades.every(upgrade => upgrade.automerge);
   config.blockedByPin = config.upgrades.every(upgrade => upgrade.blockedByPin);
   if (config.upgrades.every(upgrade => upgrade.updateType === 'pin')) {
diff --git a/test/workers/repository/init/__snapshots__/vulnerability.spec.js.snap b/test/workers/repository/init/__snapshots__/vulnerability.spec.js.snap
index eeaafc8e18894f1a131f59d9ebe2a58929956c17..fb5801b2fbdca0b7520367dbcba9fa43b722c5c3 100644
--- a/test/workers/repository/init/__snapshots__/vulnerability.spec.js.snap
+++ b/test/workers/repository/init/__snapshots__/vulnerability.spec.js.snap
@@ -7,6 +7,7 @@ Array [
     "force": Object {
       "commitMessageSuffix": "[SECURITY]",
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
       "vulnerabilityAlert": true,
     },
diff --git a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
index d27e526bb403a25e8ba7b20a85c75d7ce4011e0d..4715ef96c4fc139eeb8f8d872e4537d18077d284 100644
--- a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
+++ b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
@@ -87,6 +87,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],
@@ -177,6 +178,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],
@@ -267,6 +269,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],
@@ -357,6 +360,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],
@@ -447,6 +451,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],
@@ -537,6 +542,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],
@@ -627,6 +633,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],
@@ -717,6 +724,7 @@ Array [
       "commitMessageSuffix": "[SECURITY]",
       "enabled": true,
       "groupName": null,
+      "masterIssueApproval": false,
       "schedule": Array [],
     },
     "warnings": Array [],