diff --git a/src/bin/cook.rs b/src/bin/cook.rs
index aebe86841a1d7440bcba40e2b1b21c95abf7f3eb..03e5c0892b1be7a3698c01238ec01ed20dfece60 100644
--- a/src/bin/cook.rs
+++ b/src/bin/cook.rs
@@ -4,8 +4,9 @@ use cookbook::sha256::sha256_progress;
 use std::{
     env,
     fs,
-    path::Path,
-    process::{self, Command},
+    io::Write,
+    path::{Path, PathBuf},
+    process::{self, Command, Stdio},
 };
 use termion::{color, style};
 
@@ -57,7 +58,7 @@ fn run_command(mut command: process::Command) -> Result<(), String> {
     Ok(())
 }
 
-fn fetch(source: &SourceRecipe, recipe_dir: &Path) -> Result<(), String> {
+fn fetch(recipe_dir: &Path, source: &SourceRecipe) -> Result<PathBuf, String> {
     let source_dir = recipe_dir.join("source");
     match source {
         SourceRecipe::Git { git, upstream, branch, rev } => {
@@ -130,7 +131,7 @@ fn fetch(source: &SourceRecipe, recipe_dir: &Path) -> Result<(), String> {
             command.arg("submodule").arg("update").arg("--init").arg("--recursive");
             run_command(command)?;
         },
-        SourceRecipe::Tar { tar, blake3, sha256 } => {
+        SourceRecipe::Tar { tar, blake3, sha256, patches } => {
             if ! source_dir.is_dir() {
                 // Download tar
                 //TODO: replace wget
@@ -197,15 +198,77 @@ fn fetch(source: &SourceRecipe, recipe_dir: &Path) -> Result<(), String> {
                 command.arg("--verbose");
                 command.arg("--file").arg(&source_tar);
                 command.arg("--directory").arg(&source_dir_tmp);
+                command.arg("--strip-components").arg("1");
                 run_command(command)?;
 
+                // Apply patches
+                for patch_name in patches {
+                    let patch_file = recipe_dir.join(&patch_name);
+                    if ! patch_file.is_file() {
+                        return Err(format!(
+                            "failed to find patch file '{}'",
+                            patch_file.display()
+                        ));
+                    }
+
+                    let patch = fs::read_to_string(&patch_file).map_err(|err| format!(
+                        "failed to read patch file '{}': {}\n{:#?}",
+                        patch_file.display(),
+                        err,
+                        err
+                    ))?;
+
+                    let mut command = Command::new("patch");
+                    command.arg("--directory").arg(&source_dir_tmp);
+                    command.arg("--strip=1");
+                    command.stdin(Stdio::piped());
+
+                    let mut child = command.spawn().map_err(|err| format!(
+                        "failed to spawn {:?}: {}\n{:#?}",
+                        command,
+                        err,
+                        err
+                    ))?;
+
+                    if let Some(ref mut stdin) = child.stdin {
+                        stdin.write_all(patch.as_bytes()).map_err(|err| format!(
+                            "failed to write stdin of {:?}: {}\n{:#?}",
+                            command,
+                            err,
+                            err
+                        ))?;
+                    } else {
+                        return Err(format!(
+                            "failed to find stdin of {:?}",
+                            command
+                        ));
+                    }
+
+                    let status = child.wait().map_err(|err| format!(
+                        "failed to run {:?}: {}\n{:#?}",
+                        command,
+                        err,
+                        err
+                    ))?;
+
+                    if ! status.success() {
+                        return Err(format!(
+                            "failed to run {:?}: exited with status {}",
+                            command,
+                            status
+                        ));
+                    }
+
+                    run_command(command)?;
+                }
+
                 // Move source.tmp to source atomically
                 rename(&source_dir_tmp, &source_dir)?;
             }
         }
     }
 
-    Ok(())
+    Ok(source_dir)
 }
 
 fn cook(recipe_name: &str) -> Result<(), String> {
@@ -240,7 +303,7 @@ fn cook(recipe_name: &str) -> Result<(), String> {
         err
     ))?;
 
-    fetch(&recipe.source, &recipe_dir).map_err(|err| format!(
+    let source_dir = fetch(&recipe_dir, &recipe.source).map_err(|err| format!(
         "failed to fetch: {}",
         err
     ))?;
diff --git a/src/recipe.rs b/src/recipe.rs
index 6ec4a918e5a4437ebe6bd02d0aa9ace1f326292c..da6db88aa0db0094635b7d8a21b38f408ba3c8f1 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -26,6 +26,9 @@ pub enum SourceRecipe {
         blake3: Option<String>,
         /// The optional sha256 sum of the tar file. This is a slower alternative to a blake3 sum
         sha256: Option<String>,
+        /// A list of patch files to apply to the source
+        #[serde(default)]
+        patches: Vec<String>,
     },
 }
 
@@ -48,9 +51,6 @@ pub enum BuildRecipe {
 pub struct Recipe {
     /// Specifies how to donload the source for this recipe
     pub source: SourceRecipe,
-    /// A list of patch files to apply to the source
-    #[serde(default)]
-    pub patches: Vec<String>,
     /// Specifies how to build this recipe
     pub build: BuildRecipe,
 }