diff --git a/docs/usage/self-hosting.md b/docs/usage/self-hosting.md
index d213d0826aa767e7e772d9c3a97f1fae07f0840e..6ee632fef2eac4177c95bad0283e423e85b5d3d7 100644
--- a/docs/usage/self-hosting.md
+++ b/docs/usage/self-hosting.md
@@ -196,11 +196,18 @@ Self-hosted Renovate can be configured using any of the following (or a combinat
 
 - A `config.js` file (can also be named `config.json`, but you can't have both at the same time)
 - CLI parameters
-- Environment parameters
+- Environment variables
 
 Note that some Renovate configuration options are _only_ available for self-hosting, and so can only be configured using one of the above methods.
 These are described in the [Self-hosted Configuration](./self-hosted-configuration.md) doc.
 
+If you are configuring using environment variables, there are two possibilities:
+
+- Upper-cased, camel-cased, `RENOVATE_`-prefixed single config options like `RENOVATE_TOKEN=abc123` or `RENOVATE_GIT_AUTHOR=a@b.com`
+- Set `RENOVATE_CONFIG` to a [stringified](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) version of the full JSON config, e.g. `RENOVATE_CONFIG='{"token":"abc123","gitAuthor":"a@b.com"}'`
+
+If you combine both of the above then any single config option in the environment variable will override what's in `RENOVATE_CONFIG`.
+
 ## Authentication
 
 Regardless of platform, you need to select a user account for `renovate` to assume the identity of, and generate a Personal Access Token.
diff --git a/lib/config/env.spec.ts b/lib/config/env.spec.ts
index 8e51fbcdff65fffae449243da557645235c50ba4..13d2311362fac115a229cfc10bf2452ce4288918 100644
--- a/lib/config/env.spec.ts
+++ b/lib/config/env.spec.ts
@@ -116,6 +116,15 @@ describe('config/env', () => {
       };
       expect(env.getConfig(envParam)).toMatchSnapshot();
     });
+    it('merges full config from env', () => {
+      const envParam: NodeJS.ProcessEnv = {
+        RENOVATE_CONFIG: '{"enabled":false,"token":"foo"}',
+        RENOVATE_TOKEN: 'a',
+      };
+      const config = env.getConfig(envParam);
+      expect(config.enabled).toBe(false);
+      expect(config.token).toBe('a');
+    });
   });
   describe('.getEnvName(definition)', () => {
     it('returns empty', () => {
diff --git a/lib/config/env.ts b/lib/config/env.ts
index 8a7f96c0dd9d64a65132abde9d977c86b37d7a05..cb7dcf10a4713db8d2078cf0f9f2925803bc5337 100644
--- a/lib/config/env.ts
+++ b/lib/config/env.ts
@@ -20,7 +20,19 @@ export function getEnvName(option: Partial<RenovateOptions>): string {
 export function getConfig(env: NodeJS.ProcessEnv): GlobalConfig {
   const options = getOptions();
 
-  const config: GlobalConfig = { hostRules: [] };
+  let config: GlobalConfig = {};
+
+  if (env.RENOVATE_CONFIG) {
+    try {
+      config = JSON.parse(env.RENOVATE_CONFIG);
+      logger.debug({ config }, 'Detected config in env RENOVATE_CONFIG');
+    } catch (err) /* istanbul ignore next */ {
+      logger.fatal({ err }, 'Could not parse RENOVATE_CONFIG');
+      process.exit(1);
+    }
+  }
+
+  config.hostRules ||= [];
 
   const coersions = {
     boolean: (val: string): boolean => val === 'true',