Skip to content

Commit a467f75

Browse files
committed
Fix duplicate challenge containers on server restart
Clean up old challenge containers with different suffixes when starting a new challenge. This handles the case where the server restarts with a new container ID, which would previously leave orphaned containers.
1 parent d4c4f2e commit a467f75

File tree

1 file changed

+47
-1
lines changed

1 file changed

+47
-1
lines changed

crates/challenge-orchestrator/src/docker.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,9 +437,17 @@ impl DockerClient {
437437
"Generated challenge container name"
438438
);
439439

440-
// Remove existing container if any
440+
// Remove existing container if any (same name)
441441
let _ = self.remove_container(&container_name).await;
442442

443+
// Clean up old challenge containers with different suffixes (from previous server restarts)
444+
let challenge_prefix = format!(
445+
"challenge-{}-",
446+
config.name.to_lowercase().replace(' ', "-")
447+
);
448+
self.cleanup_old_challenge_containers(&challenge_prefix, &container_name)
449+
.await;
450+
443451
// Build port bindings - expose on a dynamic port
444452
let mut port_bindings = HashMap::new();
445453
port_bindings.insert(
@@ -732,6 +740,44 @@ impl DockerClient {
732740
Ok(output)
733741
}
734742

743+
/// Clean up old challenge containers with different suffixes
744+
/// This handles the case where the server restarts with a new container ID
745+
async fn cleanup_old_challenge_containers(&self, prefix: &str, exclude_name: &str) {
746+
let options = ListContainersOptions::<String> {
747+
all: true,
748+
..Default::default()
749+
};
750+
751+
let containers = match self.docker.list_containers(Some(options)).await {
752+
Ok(c) => c,
753+
Err(e) => {
754+
warn!(error = %e, "Failed to list containers for cleanup");
755+
return;
756+
}
757+
};
758+
759+
for container in containers {
760+
let names = container.names.unwrap_or_default();
761+
let container_id = match container.id.as_ref() {
762+
Some(id) => id.clone(),
763+
None => continue,
764+
};
765+
766+
// Check if container name matches prefix but is not the current container
767+
let should_remove = names.iter().any(|name| {
768+
let clean_name = name.trim_start_matches('/');
769+
clean_name.starts_with(prefix) && clean_name != exclude_name
770+
});
771+
772+
if should_remove {
773+
info!(container = ?names, "Removing old challenge container from previous server instance");
774+
if let Err(e) = self.remove_container(&container_id).await {
775+
warn!(container = ?names, error = %e, "Failed to remove old container");
776+
}
777+
}
778+
}
779+
}
780+
735781
/// Clean up stale task containers created by challenge evaluations
736782
///
737783
/// This removes containers that match the pattern but excludes:

0 commit comments

Comments
 (0)