diff --git a/src/bin/installer.rs b/src/bin/installer.rs
index eb43526e0fb42b8ad27dc7ccf03a6588df759593..bd87130d49727c5ae8cc99b85205295e74cf9552 100644
--- a/src/bin/installer.rs
+++ b/src/bin/installer.rs
@@ -3,13 +3,13 @@ extern crate redox_installer;
 extern crate serde;
 extern crate toml;
 
-use std::io::{Read, Write};
+use std::io::Write;
 use std::path::Path;
 use std::{env, fs, io, process};
 
 use arg_parser::ArgParser;
 
-use redox_installer::PackageConfig;
+use redox_installer::{Config, PackageConfig};
 
 fn main() {
     let stderr = io::stderr();
@@ -27,24 +27,11 @@ fn main() {
     // If not set on the command line or the filesystem config, then build packages from source.
     let repo_binary = parser.found("repo-binary");
 
-    let mut config_data = String::new();
     let mut config = if let Some(path) = parser.get_opt("config") {
-        match fs::File::open(&path) {
-            Ok(mut config_file) => match config_file.read_to_string(&mut config_data) {
-                Ok(_) => match toml::from_str(&config_data) {
-                    Ok(config) => config,
-                    Err(err) => {
-                        writeln!(stderr, "installer: {}: failed to decode: {}", path, err).unwrap();
-                        process::exit(1);
-                    }
-                },
-                Err(err) => {
-                    writeln!(stderr, "installer: {}: failed to read: {}", path, err).unwrap();
-                    process::exit(1);
-                }
-            },
+        match Config::from_file(Path::new(&path)) {
+            Ok(config) => config,
             Err(err) => {
-                writeln!(stderr, "installer: {}: failed to open: {}", path, err).unwrap();
+                writeln!(stderr, "installer: {err}").unwrap();
                 process::exit(1);
             }
         }
@@ -55,7 +42,7 @@ fn main() {
     // Add filesystem.toml to config
     config.files.push(redox_installer::FileConfig {
         path: "filesystem.toml".to_string(),
-        data: config_data,
+        data: toml::to_string_pretty(&config).unwrap(),
         ..Default::default()
     });
 
diff --git a/src/bin/installer_tui.rs b/src/bin/installer_tui.rs
index 74067a542eafa416363029903c227b3bc0b746f3..fb58264e75a88c4d6125fc4bbb4a3f864ba20dc2 100644
--- a/src/bin/installer_tui.rs
+++ b/src/bin/installer_tui.rs
@@ -309,24 +309,7 @@ fn main() {
         efi_partition_size: None,
     };
     let res = with_whole_disk(&disk_path, &disk_option, |mount_path| -> Result<(), failure::Error> {
-        let mut config: Config = {
-            let path = root_path.join("filesystem.toml");
-            match fs::read_to_string(&path) {
-                Ok(config_data) => {
-                    match toml::from_str(&config_data) {
-                        Ok(config) => {
-                            config
-                        },
-                        Err(err) => {
-                            return Err(format_err!("{}: failed to decode: {}", path.display(), err));
-                        }
-                    }
-                },
-                Err(err) => {
-                    return Err(format_err!("{}: failed to read: {}", path.display(), err));
-                }
-            }
-        };
+        let mut config: Config = Config::from_file(&root_path.join("filesystem.toml"))?;
 
         // Copy filesystem.toml, which is not packaged
         let mut files = vec![
diff --git a/src/config/file.rs b/src/config/file.rs
index 21565c36590d585804e1145668cd15f93b7c216b..7672f908d506e0c240ca82ae10f4ab43d0c28c07 100644
--- a/src/config/file.rs
+++ b/src/config/file.rs
@@ -29,7 +29,7 @@ fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, recursive: bool) -> Re
     Ok(())
 }
 
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
 pub struct FileConfig {
     pub path: String,
     pub data: String,
diff --git a/src/config/general.rs b/src/config/general.rs
index 59099bbcdba51403679014e17eeab8d78fdff83a..7ed6c33f5efa1367aa09ab4e0bad295306c458df 100644
--- a/src/config/general.rs
+++ b/src/config/general.rs
@@ -1,7 +1,15 @@
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
 pub struct GeneralConfig {
     pub prompt: bool,
     // Allow config to specify cookbook recipe or binary package as default
     pub repo_binary: Option<bool>,
     pub efi_partition_size: Option<u32>, //MiB
 }
+
+impl GeneralConfig {
+    pub(super) fn merge(&mut self, other: GeneralConfig) {
+        self.prompt = other.prompt;
+        self.repo_binary = other.repo_binary.or(self.repo_binary);
+        self.efi_partition_size = other.efi_partition_size.or(self.efi_partition_size);
+    }
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 6172d4a86def8b185620a2a4100ba726982c16c2..e6ff206cb23f8184718157149486853f10d82957 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -1,12 +1,17 @@
 use std::collections::BTreeMap;
+use std::fs;
+use std::mem;
+use std::path::{Path, PathBuf};
 
-pub mod general;
 pub mod file;
+pub mod general;
 pub mod package;
 pub mod user;
 
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
 pub struct Config {
+    #[serde(default)]
+    pub include: Vec<PathBuf>,
     pub general: general::GeneralConfig,
     #[serde(default)]
     pub packages: BTreeMap<String, package::PackageConfig>,
@@ -15,3 +20,60 @@ pub struct Config {
     #[serde(default)]
     pub users: BTreeMap<String, user::UserConfig>,
 }
+
+impl Config {
+    pub fn from_file(path: &Path) -> Result<Self, failure::Error> {
+        let mut config: Config = match fs::read_to_string(&path) {
+            Ok(config_data) => match toml::from_str(&config_data) {
+                Ok(config) => config,
+                Err(err) => {
+                    return Err(format_err!("{}: failed to decode: {}", path.display(), err));
+                }
+            },
+            Err(err) => {
+                return Err(format_err!("{}: failed to read: {}", path.display(), err));
+            }
+        };
+
+        let config_dir = path.parent().unwrap();
+
+        let mut configs = mem::take(&mut config.include)
+            .into_iter()
+            .map(|path| Config::from_file(&config_dir.join(path)))
+            .collect::<Result<Vec<Config>, failure::Error>>()?;
+        configs.push(config); // Put ourself last to ensure that it overwrites anything else.
+
+        config = configs.remove(0);
+
+        for other_config in configs {
+            config.merge(other_config);
+        }
+
+        Ok(config)
+    }
+
+    pub fn merge(&mut self, other: Config) {
+        assert!(self.include.is_empty());
+        assert!(other.include.is_empty());
+
+        let Config {
+            include: _,
+            general: other_general,
+            packages: other_packages,
+            files: other_files,
+            users: other_users,
+        } = other;
+
+        self.general.merge(other_general);
+
+        for (package, package_config) in other_packages {
+            self.packages.insert(package, package_config);
+        }
+
+        self.files.extend(other_files);
+
+        for (user, user_config) in other_users {
+            self.users.insert(user, user_config);
+        }
+    }
+}
diff --git a/src/config/package.rs b/src/config/package.rs
index f2df773caabb1ffd476b2be2840796e3c11e87e9..5f37196fe5d927d4fa9749e5fc04896b0639cf72 100644
--- a/src/config/package.rs
+++ b/src/config/package.rs
@@ -1,4 +1,4 @@
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Deserialize, Serialize)]
 #[serde(untagged)]
 pub enum PackageConfig {
     Empty,
diff --git a/src/config/user.rs b/src/config/user.rs
index 4886f4f968cf89934749ab83285704ee486ba85b..3b823420481d0a835e0878bc51c06db8eebb73ca 100644
--- a/src/config/user.rs
+++ b/src/config/user.rs
@@ -1,4 +1,4 @@
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
 pub struct UserConfig {
     pub password: Option<String>,
     pub uid: Option<u32>,
diff --git a/test.toml b/test.toml
index 02376f01545c115f1500840df9c557401256ea8b..39c0082dcf598173f1ea3e4902896e2ebfddece5 100644
--- a/test.toml
+++ b/test.toml
@@ -137,11 +137,6 @@ path = "/usr/bin"
 data = "../bin"
 symlink = true
 
-[[files]]
-path = "/usr/games"
-data = "../games"
-symlink = true
-
 [[files]]
 path = "/usr/include"
 data = "../include"