diff --git a/src/config.ts b/src/config.ts
index 166604317accbb842d349afe1ca26eb5c4500247..2f0eb76241cfca553350e116ea39b9b45e72d171 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+const ENV_PREFIX = "APPSERVICE_DISCORD";
+const ENV_KEY_SEPARATOR = "_";
+const ENV_VAL_SEPARATOR = ",";
+
 /** Type annotations for config/config.schema.yaml */
 export class DiscordBridgeConfig {
     public bridge: DiscordBridgeConfigBridge = new DiscordBridgeConfigBridge();
@@ -27,18 +31,48 @@ export class DiscordBridgeConfig {
 
     /**
      * Apply a set of keys and values over the default config.
-     * @param _config Config keys
+     * @param newConfig Config keys
      * @param configLayer Private parameter
      */
     // tslint:disable-next-line no-any
-    public ApplyConfig(newConfig: {[key: string]: any}, configLayer: any = this) {
+    public applyConfig(newConfig: {[key: string]: any}, configLayer: {[key: string]: any} = this) {
           Object.keys(newConfig).forEach((key) => {
-            if ( typeof(configLayer[key]) === "object" &&
-                    !Array.isArray(configLayer[key])) {
-                this.ApplyConfig(newConfig[key], this[key]);
-                return;
+            if (configLayer[key] instanceof Object && !(configLayer[key] instanceof Array)) {
+                this.applyConfig(newConfig[key], configLayer[key]);
+            } else {
+                configLayer[key] = newConfig[key];
+            }
+        });
+    }
+
+    /**
+     * Override configuration keys defined in the supplied environment dictionary.
+     * @param environment environment variable dictionary
+     * @param path private parameter: config layer path determining the environment key prefix
+     * @param configLayer private parameter: current layer of configuration to alter recursively
+     */
+    public applyEnvironmentOverrides(
+        // tslint:disable-next-line no-any
+        environment: {[key: string]: any},
+        path: string[] = [ENV_PREFIX],
+        // tslint:disable-next-line no-any
+        configLayer: {[key: string]: any} = this,
+    ) {
+        Object.keys(configLayer).forEach((key) => {
+            // camelCase to THICK_SNAKE
+            const attributeKey = key.replace(/[A-Z]/g, (prefix) => `${ENV_KEY_SEPARATOR}${prefix}`).toUpperCase();
+            const attributePath = path.concat([attributeKey]);
+
+            if (configLayer[key] instanceof Object && !(configLayer[key] instanceof Array)) {
+                this.applyEnvironmentOverrides(environment, attributePath, configLayer[key]);
+            } else {
+                const lookupKey = attributePath.join(ENV_KEY_SEPARATOR);
+                if (lookupKey in environment) {
+                    configLayer[key] = (configLayer[key] instanceof Array)
+                        ? environment[lookupKey].split(ENV_VAL_SEPARATOR)
+                        : environment[lookupKey];
+                }
             }
-            configLayer[key] = newConfig[key];
         });
     }
 }
diff --git a/src/discordas.ts b/src/discordas.ts
index 0f4b5e11d6a46c238e1e259316d23b11e06c4da2..1f350c3819a8f6c70c924bbc1f289b7af6f92276 100644
--- a/src/discordas.ts
+++ b/src/discordas.ts
@@ -59,7 +59,8 @@ type callbackFn = (...args: any[]) => Promise<any>;
 
 async function run(port: number, fileConfig: DiscordBridgeConfig) {
     const config = new DiscordBridgeConfig();
-    config.ApplyConfig(fileConfig);
+    config.applyConfig(fileConfig);
+    config.applyEnvironmentOverrides(process.env);
     Log.Configure(config.logging);
     log.info("Starting Discord AS");
     const yamlConfig = yaml.safeLoad(fs.readFileSync(cli.opts.registrationPath, "utf8"));
diff --git a/test/test_config.ts b/test/test_config.ts
index 26b037ef2bf6330ec0fa098f46ed2953e015fb5d..badb662a5c4a9929bfdfacb22c45faeef09a965d 100644
--- a/test/test_config.ts
+++ b/test/test_config.ts
@@ -22,10 +22,10 @@ import { DiscordBridgeConfig } from "../src/config";
 
 const expect = Chai.expect;
 
-describe("DiscordBridgeConfig.ApplyConfig", () => {
+describe("DiscordBridgeConfig.applyConfig", () => {
     it("should merge configs correctly", () => {
         const config = new DiscordBridgeConfig();
-        config.ApplyConfig({
+        config.applyConfig({
             bridge: {
                 disableDeletionForwarding: true,
                 disableDiscordMentions: false,
@@ -46,9 +46,32 @@ describe("DiscordBridgeConfig.ApplyConfig", () => {
         expect(config.bridge.disableJoinLeaveNotifications).to.be.true;
         expect(config.logging.console).to.equal("warn");
     });
+    it("should merge environment overrides correctly", () => {
+        const config = new DiscordBridgeConfig();
+        config.applyConfig({
+            bridge: {
+                disableDeletionForwarding: true,
+                disableDiscordMentions: false,
+                homeserverUrl: "blah",
+            },
+            logging: {
+                console: "warn",
+            },
+        });
+        config.applyEnvironmentOverrides({
+            APPSERVICE_DISCORD_BRIDGE_DISABLE_DELETION_FORWARDING: false,
+            APPSERVICE_DISCORD_BRIDGE_DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
+            APPSERVICE_DISCORD_LOGGING_CONSOLE: "debug",
+        });
+        expect(config.bridge.disableJoinLeaveNotifications).to.be.true;
+        expect(config.bridge.disableDeletionForwarding).to.be.false;
+        expect(config.bridge.disableDiscordMentions).to.be.false;
+        expect(config.bridge.homeserverUrl).to.equal("blah");
+        expect(config.logging.console).to.equal("debug");
+    });
     it("should merge logging.files correctly", () => {
         const config = new DiscordBridgeConfig();
-        config.ApplyConfig({
+        config.applyConfig({
             logging: {
                 console: "silent",
                 files: [
diff --git a/tools/chanfix.ts b/tools/chanfix.ts
index 8d4f4abf9b8510b3ef666184ee640170836fda11..a3f5095231f489dc78aea5c87d4ed20911b9d2f2 100644
--- a/tools/chanfix.ts
+++ b/tools/chanfix.ts
@@ -67,7 +67,8 @@ if (options.help) {
 const yamlConfig = yaml.safeLoad(fs.readFileSync("./discord-registration.yaml", "utf8"));
 const registration = AppServiceRegistration.fromObject(yamlConfig);
 const config = new DiscordBridgeConfig();
-config.ApplyConfig(yaml.safeLoad(fs.readFileSync(options.config, "utf8")) as DiscordBridgeConfig);
+config.applyConfig(yaml.safeLoad(fs.readFileSync(options.config, "utf8")) as DiscordBridgeConfig);
+config.applyEnvironmentOverrides(process.env);
 
 if (registration === null) {
     throw new Error("Failed to parse registration file");
diff --git a/tools/ghostfix.ts b/tools/ghostfix.ts
index 9ce61d6d6102b4bf61566a0379aaae92b085c884..b4e99e9dc95eaf4aab792f9ad39c4f99c2bc7588 100644
--- a/tools/ghostfix.ts
+++ b/tools/ghostfix.ts
@@ -76,7 +76,8 @@ if (options.help) {
 const yamlConfig = yaml.safeLoad(fs.readFileSync("./discord-registration.yaml", "utf8"));
 const registration = AppServiceRegistration.fromObject(yamlConfig);
 const config = new DiscordBridgeConfig();
-config.ApplyConfig(yaml.safeLoad(fs.readFileSync(options.config, "utf8")) as DiscordBridgeConfig);
+config.applyConfig(yaml.safeLoad(fs.readFileSync(options.config, "utf8")) as DiscordBridgeConfig);
+config.applyEnvironmentOverrides(process.env);
 
 if (registration === null) {
     throw new Error("Failed to parse registration file");