diff --git a/.github/workflows/sync-common.yml b/.github/workflows/sync-common.yml
index c3cca72..7b83091 100644
--- a/.github/workflows/sync-common.yml
+++ b/.github/workflows/sync-common.yml
@@ -110,10 +110,22 @@ jobs:
           name: sync-common
           path: .
 
+      - name: Determine exclusions
+        id: exclusions
+        run: |
+          case "${{ matrix.full_name }}" in
+            bootc-dev/bink|bootc-dev/bootc-operator)
+              echo "args=--exclude .devcontainer/" >> "$GITHUB_OUTPUT"
+              ;;
+            *)
+              echo "args=" >> "$GITHUB_OUTPUT"
+              ;;
+          esac
+
       - name: Sync common files to repository
         run: |
           chmod +x sync-common
-          ./sync-common infra repo "${{ github.sha }}"
+          ./sync-common infra repo "${{ github.sha }}" ${{ steps.exclusions.outputs.args }}
 
       - name: Open pull request
         uses: peter-evans/create-pull-request@v8
diff --git a/scripts/sync-common/Cargo.lock b/scripts/sync-common/Cargo.lock
index 09d5ec3..373e87b 100644
--- a/scripts/sync-common/Cargo.lock
+++ b/scripts/sync-common/Cargo.lock
@@ -2,6 +2,56 @@
 # It is not intended for manual editing.
 version = 4
 
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys",
+]
+
 [[package]]
 name = "anyhow"
 version = "1.0.103"
@@ -20,6 +70,52 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
 
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
 [[package]]
 name = "errno"
 version = "0.3.14"
@@ -48,6 +144,18 @@ dependencies = [
  "wasip2",
 ]
 
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
 [[package]]
 name = "libc"
 version = "0.2.186"
@@ -66,6 +174,30 @@ version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
 
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
+dependencies = [
+ "proc-macro2",
+]
+
 [[package]]
 name = "r-efi"
 version = "5.3.0"
@@ -85,11 +217,29 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
 [[package]]
 name = "sync-common"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "clap",
  "tempfile",
  "xshell",
 ]
@@ -107,6 +257,18 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
 [[package]]
 name = "wasip2"
 version = "1.0.1+wasi-0.2.4"
diff --git a/scripts/sync-common/Cargo.toml b/scripts/sync-common/Cargo.toml
index 99b1c04..e524a3d 100644
--- a/scripts/sync-common/Cargo.toml
+++ b/scripts/sync-common/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 anyhow = "1.0"
+clap = { version = "4", features = ["derive"] }
 xshell = "0.2"
 
 [dev-dependencies]
diff --git a/scripts/sync-common/src/main.rs b/scripts/sync-common/src/main.rs
index 0bf4945..ee7ebad 100644
--- a/scripts/sync-common/src/main.rs
+++ b/scripts/sync-common/src/main.rs
@@ -1,7 +1,22 @@
 use anyhow::{Context, Result};
+use clap::Parser;
 use std::path::{Path, PathBuf};
 use xshell::{cmd, Shell};
 
+#[derive(Parser)]
+#[command(about = "Sync common files from infra to target repositories")]
+struct Args {
+    /// Path to the infra repository
+    infra_path: PathBuf,
+    /// Path to the target repository
+    target_path: PathBuf,
+    /// Git commit SHA to mark as synced
+    current_commit: String,
+    /// Patterns to exclude from syncing (passed to rsync --exclude)
+    #[arg(long)]
+    exclude: Vec<String>,
+}
+
 const COMMIT_MARKER: &str = ".bootc-dev-infra-commit.txt";
 
 /// Git operations for querying repository history
@@ -84,16 +99,25 @@ impl FileOps {
     }
 
     /// Sync directory using rsync
-    fn sync_directory(sh: &Shell, source: &Path, target: &Path) -> Result<()> {
+    fn sync_directory(sh: &Shell, source: &Path, target: &Path, excludes: &[String]) -> Result<()> {
         let source_str = format!("{}/", source.display());
         let target_str = target.display().to_string();
 
-        cmd!(sh, "rsync -av {source_str} {target_str}")
+        let mut rsync_args = vec!["-av".to_string()];
+        for pattern in excludes {
+            rsync_args.push("--exclude".to_string());
+            rsync_args.push(pattern.clone());
+        }
+        rsync_args.push(source_str);
+        rsync_args.push(target_str);
+
+        cmd!(sh, "rsync {rsync_args...}")
             .run()
             .context("Failed to sync directory with rsync")?;
 
         Ok(())
     }
+
 }
 
 /// Main syncer that orchestrates the sync process
@@ -105,6 +129,7 @@ impl CommonFileSyncer {
         infra_path: &Path,
         target_path: &Path,
         current_commit: &str,
+        excludes: &[String],
     ) -> Result<bool> {
         let common_path = infra_path.join("common");
         if !common_path.exists() {
@@ -120,8 +145,9 @@ impl CommonFileSyncer {
                 &common_path,
                 &prev,
                 current_commit,
+                excludes,
             ),
-            None => Self::sync_initial(target_path, &common_path, current_commit),
+            None => Self::sync_initial(target_path, &common_path, current_commit, excludes),
         }
     }
 
@@ -132,6 +158,7 @@ impl CommonFileSyncer {
         common_path: &Path,
         previous_commit: &str,
         current_commit: &str,
+        excludes: &[String],
     ) -> Result<bool> {
         println!("Previous sync: {}", previous_commit);
         println!("Current commit: {}", current_commit);
@@ -160,7 +187,7 @@ impl CommonFileSyncer {
         }
 
         // Sync all current files
-        FileOps::sync_directory(&sh, common_path, target_path)?;
+        FileOps::sync_directory(&sh, common_path, target_path, excludes)?;
 
         // Update commit marker
         FileOps::write_commit_marker(target_path, current_commit)?;
@@ -173,11 +200,12 @@ impl CommonFileSyncer {
         target_path: &Path,
         common_path: &Path,
         current_commit: &str,
+        excludes: &[String],
     ) -> Result<bool> {
         println!("First sync - copying all files");
 
         let sh = Shell::new()?;
-        FileOps::sync_directory(&sh, common_path, target_path)?;
+        FileOps::sync_directory(&sh, common_path, target_path, excludes)?;
         FileOps::write_commit_marker(target_path, current_commit)?;
 
         Ok(true)
@@ -185,18 +213,14 @@ impl CommonFileSyncer {
 }
 
 fn main() -> Result<()> {
-    let args: Vec<String> = std::env::args().collect();
-
-    if args.len() != 4 {
-        eprintln!("Usage: {} <infra-path> <target-path> <current-commit>", args[0]);
-        std::process::exit(1);
-    }
+    let args = Args::parse();
 
-    let infra_path = PathBuf::from(&args[1]);
-    let target_path = PathBuf::from(&args[2]);
-    let current_commit = &args[3];
-
-    CommonFileSyncer::sync(&infra_path, &target_path, current_commit)?;
+    CommonFileSyncer::sync(
+        &args.infra_path,
+        &args.target_path,
+        &args.current_commit,
+        &args.exclude,
+    )?;
 
     Ok(())
 }
@@ -262,6 +286,7 @@ mod tests {
             infra_dir.path(),
             target_dir.path(),
             "abc123",
+            &[],
         );
 
         assert!(result.is_err());
@@ -304,6 +329,7 @@ mod tests {
             infra_dir.path(),
             target_dir.path(),
             &commit,
+            &[],
         );
 
         assert!(result.is_ok());
@@ -330,7 +356,7 @@ mod tests {
         let initial_commit = setup_infra_repo(infra_dir.path());
 
         // Initial sync
-        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit).unwrap();
+        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit, &[]).unwrap();
 
         // Add a new file to common/
         let sh = Shell::new().unwrap();
@@ -348,6 +374,7 @@ mod tests {
             infra_dir.path(),
             target_dir.path(),
             &new_commit,
+            &[],
         );
 
         assert!(result.is_ok());
@@ -369,7 +396,7 @@ mod tests {
         let initial_commit = setup_infra_repo(infra_dir.path());
 
         // Initial sync
-        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit).unwrap();
+        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit, &[]).unwrap();
 
         // Verify file2.txt exists
         assert!(target_dir.path().join("file2.txt").exists());
@@ -389,6 +416,7 @@ mod tests {
             infra_dir.path(),
             target_dir.path(),
             &new_commit,
+            &[],
         );
 
         assert!(result.is_ok());
@@ -408,13 +436,14 @@ mod tests {
         let commit = setup_infra_repo(infra_dir.path());
 
         // Initial sync
-        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &commit).unwrap();
+        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &commit, &[]).unwrap();
 
         // Sync again with same commit (no changes)
         let result = CommonFileSyncer::sync(
             infra_dir.path(),
             target_dir.path(),
             &commit,
+            &[],
         );
 
         assert!(result.is_ok());
@@ -432,7 +461,7 @@ mod tests {
         fs::write(target_dir.path().join("repo-specific.txt"), "local content").unwrap();
 
         // Initial sync
-        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit).unwrap();
+        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &initial_commit, &[]).unwrap();
 
         // Verify repo-specific file still exists
         assert!(target_dir.path().join("repo-specific.txt").exists());
@@ -445,4 +474,30 @@ mod tests {
         assert!(target_dir.path().join("file1.txt").exists());
         assert!(target_dir.path().join("file2.txt").exists());
     }
+
+    #[test]
+    fn test_initial_sync_with_exclusions() {
+        let infra_dir = TempDir::new().unwrap();
+        let target_dir = TempDir::new().unwrap();
+
+        let commit = setup_infra_repo(infra_dir.path());
+
+        let excluded = infra_dir.path().join("common/excluded");
+        fs::create_dir(&excluded).unwrap();
+        fs::write(excluded.join("skip.txt"), "should be excluded").unwrap();
+
+        let sh = Shell::new().unwrap();
+        let _d = sh.push_dir(infra_dir.path());
+        cmd!(sh, "git add .").run().unwrap();
+        cmd!(sh, "git commit -m 'Add excluded dir'").run().unwrap();
+        let commit = cmd!(sh, "git rev-parse HEAD").read().unwrap().trim().to_string();
+
+        let excludes = vec!["excluded/".to_string()];
+        CommonFileSyncer::sync(infra_dir.path(), target_dir.path(), &commit, &excludes).unwrap();
+
+        assert!(!target_dir.path().join("excluded").exists());
+        assert!(target_dir.path().join("file1.txt").exists());
+        assert!(target_dir.path().join("file2.txt").exists());
+    }
+
 }
