You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by ma...@apache.org on 2025/11/24 12:49:17 UTC

(iggy) 01/01: refactor(config): use common config provider in server crate

This is an automated email from the ASF dual-hosted git repository.

maciej pushed a commit to branch unify-config
in repository https://gitbox.apache.org/repos/asf/iggy.git

commit eaf60d5079ffbf7b37d01e8b77a566281a317161
Author: Maciej Modzelewski <ma...@modzelewski.dev>
AuthorDate: Mon Nov 24 12:17:11 2025 +0100

    refactor(config): use common config provider in server crate
---
 core/common/src/configs/mod.rs                |  50 ++-
 core/integration/src/test_server.rs           |  12 +-
 core/integration/tests/config_provider/mod.rs |  34 +-
 core/server/src/bootstrap.rs                  |  41 ++-
 core/server/src/configs/config_provider.rs    | 464 --------------------------
 core/server/src/configs/mod.rs                |   4 +-
 core/server/src/configs/server.rs             |  73 +++-
 core/server/src/configs/validators.rs         | 120 +++----
 core/server/src/main.rs                       |   7 +-
 core/server/src/server_error.rs               |  19 +-
 10 files changed, 218 insertions(+), 606 deletions(-)

diff --git a/core/common/src/configs/mod.rs b/core/common/src/configs/mod.rs
index fa3289519..88fd498f2 100644
--- a/core/common/src/configs/mod.rs
+++ b/core/common/src/configs/mod.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -51,6 +52,7 @@ pub enum ConfigurationError {
     DefaultSerializationFailed,
     DefaultParsingFailed,
     EnvironmentVariableParsingFailed,
+    InvalidConfigurationValue,
 }
 
 impl Display for ConfigurationError {
@@ -64,6 +66,9 @@ impl Display for ConfigurationError {
             Self::EnvironmentVariableParsingFailed => {
                 write!(f, "Failed to parse environment variables")
             }
+            Self::InvalidConfigurationValue => {
+                write!(f, "Provided configuration value is invalid")
+            }
         }
     }
 }
@@ -71,7 +76,7 @@ impl Display for ConfigurationError {
 impl std::error::Error for ConfigurationError {}
 
 pub trait ConfigProvider<T: ConfigurationType> {
-    fn load_config(self) -> impl Future<Output = Result<T, ConfigurationError>>;
+    fn load_config(&self) -> impl Future<Output = Result<T, ConfigurationError>>;
 }
 
 /// File-based configuration provider that combines file, default, and environment configurations
@@ -263,12 +268,29 @@ impl<T: ConfigurationType> CustomEnvProvider<T> {
             if remaining_path.is_empty() {
                 arr[array_index] = value;
             } else if let FigmentValue::Dict(_, elem_dict) = &mut arr[array_index] {
-                Self::insert_environment_override(
-                    &Dict::new(),
-                    elem_dict,
-                    remaining_path.to_vec(),
-                    value,
-                );
+                // TODO(hubcio): this is workaround done by Claude because this code is overly
+                // complicated and I don't want to spend time on it.
+                // For nested structures in arrays, check if we need to create intermediate dicts
+                // Handle the "ports" case where it should be a nested structure
+                if remaining_path.len() >= 2 && remaining_path[0] == "ports" {
+                    // Create the ports dict if it doesn't exist
+                    elem_dict
+                        .entry("ports".to_string())
+                        .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
+
+                    if let Some(FigmentValue::Dict(_, ports_dict)) = elem_dict.get_mut("ports") {
+                        // Insert the specific port value (tcp, quic, http, websocket)
+                        ports_dict.insert(remaining_path[1].clone(), value);
+                    }
+                } else {
+                    // Default behavior for other fields
+                    Self::insert_environment_override(
+                        &Dict::new(),
+                        elem_dict,
+                        remaining_path.to_vec(),
+                        value,
+                    );
+                }
             }
         }
     }
@@ -682,7 +704,11 @@ impl<T: ConfigurationType> CustomEnvProvider<T> {
             _ => {}
         }
 
-        // Try parsing as integer
+        // Try u64 first for most numeric values (ports, byte sizes, etc.)
+        if let Ok(uint_val) = value.parse::<u64>() {
+            return FigmentValue::from(uint_val);
+        }
+        // Fall back to i64 for signed integers
         if let Ok(int_val) = value.parse::<i64>() {
             return FigmentValue::from(int_val);
         }
@@ -725,13 +751,13 @@ fn file_exists<P: AsRef<Path>>(path: P) -> bool {
 }
 
 impl<T: ConfigurationType, P: Provider + Clone> ConfigProvider<T> for FileConfigProvider<P> {
-    async fn load_config(self) -> Result<T, ConfigurationError> {
+    async fn load_config(&self) -> Result<T, ConfigurationError> {
         println!("Loading config from path: '{}'...", self.file_path);
 
         // Start with the default configuration if provided
         let mut config_builder = Figment::new();
         let has_default = self.default_config.is_some();
-        if let Some(default) = self.default_config {
+        if let Some(default) = &self.default_config {
             config_builder = config_builder.merge(default);
         } else {
             println!("No default configuration provided.");
@@ -752,7 +778,7 @@ impl<T: ConfigurationType, P: Provider + Clone> ConfigProvider<T> for FileConfig
         }
 
         // Merge environment variables into the configuration
-        config_builder = config_builder.merge(self.env_provider);
+        config_builder = config_builder.merge(self.env_provider.clone());
 
         // Finally, attempt to extract the final configuration
         let config_result: Result<T, figment::Error> = config_builder.extract();
diff --git a/core/integration/src/test_server.rs b/core/integration/src/test_server.rs
index 160094d26..a13f44e2e 100644
--- a/core/integration/src/test_server.rs
+++ b/core/integration/src/test_server.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -22,9 +23,9 @@ use derive_more::Display;
 use futures::executor::block_on;
 use iggy::prelude::UserStatus::Active;
 use iggy::prelude::*;
-use iggy_common::TransportProtocol;
+use iggy_common::{ConfigProvider, TransportProtocol};
 use rand::Rng;
-use server::configs::config_provider::{ConfigProvider, FileConfigProvider};
+use server::configs::server::ServerConfig;
 use std::collections::HashMap;
 use std::fs;
 use std::fs::{File, OpenOptions};
@@ -338,7 +339,7 @@ impl TestServer {
 
     fn wait_until_server_has_bound(&mut self) {
         let config_path = format!("{}/runtime/current_config.toml", self.local_data_path);
-        let file_config_provider = FileConfigProvider::new(config_path.clone());
+        let file_config_provider = ServerConfig::config_provider(&config_path);
 
         let max_attempts = (MAX_PORT_WAIT_DURATION_S * 1000) / SLEEP_INTERVAL_MS;
         self.server_addrs.clear();
@@ -356,7 +357,8 @@ impl TestServer {
                     sleep(Duration::from_millis(SLEEP_INTERVAL_MS));
                     continue;
                 }
-                match file_config_provider.load_config().await {
+                let config: Result<ServerConfig, _> = file_config_provider.load_config().await;
+                match config {
                     Ok(config) => {
                         // Verify config contains fresh addresses, not stale defaults
                         // Default ports: TCP=8090, HTTP=3000, QUIC=8080, WebSocket=8092
diff --git a/core/integration/tests/config_provider/mod.rs b/core/integration/tests/config_provider/mod.rs
index 882431747..82e039732 100644
--- a/core/integration/tests/config_provider/mod.rs
+++ b/core/integration/tests/config_provider/mod.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -16,12 +17,12 @@
  * under the License.
  */
 
+use iggy_common::ConfigProvider;
+use integration::file::get_root_path;
 use serial_test::serial;
-use server::configs::config_provider::{ConfigProvider, FileConfigProvider};
+use server::configs::server::ServerConfig;
 use std::env;
 
-use integration::file::get_root_path;
-
 #[serial]
 #[tokio::test]
 async fn validate_config_env_override() {
@@ -44,8 +45,9 @@ async fn validate_config_env_override() {
     }
 
     let config_path = get_root_path().join("../configs/server.toml");
-    let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
-    let config = file_config_provider
+    let file_config_provider =
+        ServerConfig::config_provider(&config_path.as_path().display().to_string());
+    let config: ServerConfig = file_config_provider
         .load_config()
         .await
         .expect("Failed to load server.toml config");
@@ -85,8 +87,9 @@ async fn validate_socket_override() {
     }
 
     let config_path = get_root_path().join("../configs/server.toml");
-    let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
-    let config = file_config_provider
+    let file_config_provider =
+        ServerConfig::config_provider(&config_path.as_path().display().to_string());
+    let config: ServerConfig = file_config_provider
         .load_config()
         .await
         .expect("Failed to load server.toml config with socket override");
@@ -113,8 +116,9 @@ async fn validate_socket_override() {
 #[tokio::test]
 async fn validate_socket_no_override() {
     let config_path = get_root_path().join("../configs/server.toml");
-    let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
-    let config = file_config_provider
+    let file_config_provider =
+        ServerConfig::config_provider(&config_path.as_path().display().to_string());
+    let config: ServerConfig = file_config_provider
         .load_config()
         .await
         .expect("Failed to load server.toml config without socket override");
@@ -198,8 +202,9 @@ async fn validate_cluster_config_env_override() {
     }
 
     let config_path = get_root_path().join("../configs/server.toml");
-    let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
-    let config = file_config_provider
+    let file_config_provider =
+        ServerConfig::config_provider(&config_path.as_path().display().to_string());
+    let config: ServerConfig = file_config_provider
         .load_config()
         .await
         .expect("Failed to load server.toml config with cluster env overrides");
@@ -388,8 +393,9 @@ async fn validate_four_node_cluster_config_env_override() {
     }
 
     let config_path = get_root_path().join("../configs/server.toml");
-    let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
-    let config = file_config_provider
+    let file_config_provider =
+        ServerConfig::config_provider(&config_path.as_path().display().to_string());
+    let config: ServerConfig = file_config_provider
         .load_config()
         .await
         .expect("Failed to load server.toml config with 4-node cluster env overrides");
diff --git a/core/server/src/bootstrap.rs b/core/server/src/bootstrap.rs
index 5f7f771ac..3f95da049 100644
--- a/core/server/src/bootstrap.rs
+++ b/core/server/src/bootstrap.rs
@@ -1,26 +1,27 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 
 use crate::{
     IGGY_ROOT_PASSWORD_ENV, IGGY_ROOT_USERNAME_ENV,
     compat::index_rebuilding::index_rebuilder::IndexRebuilder,
     configs::{
         cache_indexes::CacheIndexesConfig,
-        config_provider::ConfigProviderKind,
         server::ServerConfig,
         system::{INDEX_EXTENSION, LOG_EXTENSION, SystemConfig},
     },
@@ -266,10 +267,8 @@ pub fn create_shard_connections(
     (connectors, shutdown_handles)
 }
 
-pub async fn load_config(
-    config_provider: &ConfigProviderKind,
-) -> Result<ServerConfig, ServerError> {
-    let config = ServerConfig::load(config_provider).await?;
+pub async fn load_config() -> Result<ServerConfig, ServerError> {
+    let config = ServerConfig::load().await?;
     Ok(config)
 }
 
diff --git a/core/server/src/configs/config_provider.rs b/core/server/src/configs/config_provider.rs
deleted file mode 100644
index 3461d44ef..000000000
--- a/core/server/src/configs/config_provider.rs
+++ /dev/null
@@ -1,464 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use crate::IGGY_ROOT_PASSWORD_ENV;
-use crate::configs::server::ServerConfig;
-use crate::server_error::ConfigError;
-use figment::{
-    Error, Figment, Metadata, Profile, Provider,
-    providers::{Format, Toml},
-    value::{Dict, Map as FigmentMap, Tag, Value as FigmentValue},
-};
-use std::{env, future::Future, path::Path};
-use toml::{Value as TomlValue, map::Map};
-
-const DEFAULT_CONFIG_PROVIDER: &str = "file";
-const DEFAULT_CONFIG_PATH: &str = "configs/server.toml";
-const SECRET_KEYS: [&str; 6] = [
-    IGGY_ROOT_PASSWORD_ENV,
-    "IGGY_DATA_MAINTENANCE_ARCHIVER_S3_KEY_SECRET",
-    "IGGY_HTTP_JWT_ENCODING_SECRET",
-    "IGGY_HTTP_JWT_DECODING_SECRET",
-    "IGGY_TCP_TLS_PASSWORD",
-    "IGGY_SYSTEM_ENCRYPTION_KEY",
-];
-
-pub enum ConfigProviderKind {
-    File(FileConfigProvider),
-}
-
-impl ConfigProviderKind {
-    pub async fn load_config(&self) -> Result<ServerConfig, ConfigError> {
-        match self {
-            Self::File(p) => p.load_config().await,
-        }
-    }
-}
-
-pub trait ConfigProvider {
-    fn load_config(&self) -> impl Future<Output = Result<ServerConfig, ConfigError>>;
-}
-
-#[derive(Debug)]
-pub struct FileConfigProvider {
-    path: String,
-}
-
-pub struct CustomEnvProvider {
-    prefix: String,
-}
-
-impl FileConfigProvider {
-    pub fn new(path: String) -> Self {
-        Self { path }
-    }
-}
-
-impl CustomEnvProvider {
-    pub fn new(prefix: &str) -> Self {
-        Self {
-            prefix: prefix.to_string(),
-        }
-    }
-
-    fn walk_toml_table_to_dict(prefix: &str, table: Map<String, TomlValue>, dict: &mut Dict) {
-        for (key, value) in table {
-            let new_prefix = if prefix.is_empty() {
-                key.clone()
-            } else {
-                format!("{prefix}.{key}")
-            };
-            match value {
-                TomlValue::Table(inner_table) => {
-                    let mut nested_dict = Dict::new();
-                    Self::walk_toml_table_to_dict(&new_prefix, inner_table, &mut nested_dict);
-                    dict.insert(key, FigmentValue::from(nested_dict));
-                }
-                _ => {
-                    dict.insert(key, Self::toml_to_figment_value(&value));
-                }
-            }
-        }
-    }
-
-    fn insert_overridden_values_from_env(
-        source: &Dict,
-        target: &mut Dict,
-        keys: Vec<String>,
-        value: FigmentValue,
-    ) {
-        if keys.is_empty() {
-            return;
-        }
-
-        // Detect array patterns (e.g., cluster.nodes.0.id)
-        if let Some((idx_pos, array_index)) = Self::find_array_index(&keys) {
-            Self::handle_array_override(source, target, &keys, idx_pos, array_index, value);
-        } else {
-            Self::handle_dict_override(source, target, &keys, value);
-        }
-    }
-
-    fn find_array_index(keys: &[String]) -> Option<(usize, usize)> {
-        keys.iter()
-            .enumerate()
-            .find_map(|(i, key)| key.parse::<usize>().ok().map(|idx| (i, idx)))
-    }
-
-    fn handle_array_override(
-        source: &Dict,
-        target: &mut Dict,
-        keys: &[String],
-        idx_pos: usize,
-        array_index: usize,
-        value: FigmentValue,
-    ) {
-        let path_to_array = &keys[..idx_pos];
-        let remaining_path = &keys[idx_pos + 1..];
-
-        // Navigate to array container
-        let current_target = match Self::navigate_to_dict(
-            target,
-            &path_to_array[..path_to_array.len().saturating_sub(1)],
-        ) {
-            Some(dict) => dict,
-            None => return,
-        };
-
-        let array_key = match path_to_array.last() {
-            Some(key) => key.clone(),
-            None => return,
-        };
-
-        // Copy existing array from source if needed
-        if !current_target.contains_key(&array_key) {
-            if let Some(existing_array) = Self::find_source_array(source, path_to_array, &array_key)
-            {
-                current_target.insert(array_key.clone(), existing_array);
-            } else {
-                current_target.insert(
-                    array_key.clone(),
-                    FigmentValue::Array(Tag::Default, Vec::new()),
-                );
-            }
-        }
-
-        // Update array element
-        if let Some(FigmentValue::Array(_, arr)) = current_target.get_mut(&array_key) {
-            // Ensure array is large enough
-            while arr.len() <= array_index {
-                arr.push(FigmentValue::Dict(Tag::Default, Dict::new()));
-            }
-
-            if remaining_path.is_empty() {
-                arr[array_index] = value;
-            } else if let FigmentValue::Dict(_, elem_dict) = &mut arr[array_index] {
-                // TODO(hubcio): this is workaround done by Claude because this code is overly
-                // complicated and I don't want to spend time on it.
-                // For nested structures in arrays, check if we need to create intermediate dicts
-                // Handle the "ports" case where it should be a nested structure
-                if remaining_path.len() >= 2 && remaining_path[0] == "ports" {
-                    // Create the ports dict if it doesn't exist
-                    elem_dict
-                        .entry("ports".to_string())
-                        .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
-
-                    if let Some(FigmentValue::Dict(_, ports_dict)) = elem_dict.get_mut("ports") {
-                        // Insert the specific port value (tcp, quic, http, websocket)
-                        ports_dict.insert(remaining_path[1].clone(), value);
-                    }
-                } else {
-                    // Default behavior for other fields
-                    Self::insert_overridden_values_from_env(
-                        &Dict::new(),
-                        elem_dict,
-                        remaining_path.to_vec(),
-                        value,
-                    );
-                }
-            }
-        }
-    }
-
-    fn handle_dict_override(
-        source: &Dict,
-        target: &mut Dict,
-        keys: &[String],
-        value: FigmentValue,
-    ) {
-        let mut current_source = source;
-        let mut current_target = target;
-        let mut combined_keys = Vec::new();
-
-        for (i, key) in keys.iter().enumerate() {
-            combined_keys.push(key.clone());
-            let key_to_check = combined_keys.join("_");
-
-            match current_source.get(&key_to_check) {
-                Some(FigmentValue::Dict(_, inner_source_dict)) => {
-                    current_target
-                        .entry(key_to_check.clone())
-                        .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
-
-                    if let Some(FigmentValue::Dict(_, inner_target)) =
-                        current_target.get_mut(&key_to_check)
-                    {
-                        current_source = inner_source_dict;
-                        current_target = inner_target;
-                        combined_keys.clear();
-                    } else {
-                        return;
-                    }
-                }
-                Some(_) => {
-                    current_target.insert(key_to_check, value);
-                    return;
-                }
-                None if i == keys.len() - 1 => {
-                    current_target.insert(key_to_check, value);
-                    return;
-                }
-                _ => continue,
-            }
-        }
-    }
-
-    fn navigate_to_dict<'a>(target: &'a mut Dict, path: &[String]) -> Option<&'a mut Dict> {
-        if path.is_empty() {
-            return Some(target);
-        }
-
-        let mut current = target;
-        let mut combined_keys = Vec::new();
-
-        for key in path {
-            combined_keys.push(key.clone());
-            let key_to_check = combined_keys.join("_");
-
-            current
-                .entry(key_to_check.clone())
-                .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
-
-            match current.get_mut(&key_to_check) {
-                Some(FigmentValue::Dict(_, inner)) => {
-                    current = inner;
-                    combined_keys.clear();
-                }
-                _ => return None,
-            }
-        }
-
-        Some(current)
-    }
-
-    fn find_source_array(source: &Dict, path: &[String], array_key: &str) -> Option<FigmentValue> {
-        if path.is_empty() {
-            return None;
-        }
-
-        let mut current = source;
-        let mut combined_keys = Vec::new();
-
-        for key in &path[..path.len() - 1] {
-            combined_keys.push(key.clone());
-            let key_to_check = combined_keys.join("_");
-
-            match current.get(&key_to_check) {
-                Some(FigmentValue::Dict(_, inner)) => {
-                    current = inner;
-                    combined_keys.clear();
-                }
-                _ => return None,
-            }
-        }
-
-        current.get(array_key).cloned()
-    }
-
-    fn toml_to_figment_value(toml_value: &TomlValue) -> FigmentValue {
-        match toml_value {
-            TomlValue::String(s) => FigmentValue::from(s.clone()),
-            TomlValue::Integer(i) => FigmentValue::from(*i),
-            TomlValue::Float(f) => FigmentValue::from(*f),
-            TomlValue::Boolean(b) => FigmentValue::from(*b),
-            TomlValue::Array(arr) => {
-                let vec: Vec<FigmentValue> = arr.iter().map(Self::toml_to_figment_value).collect();
-                FigmentValue::from(vec)
-            }
-            TomlValue::Table(tbl) => {
-                let mut dict = figment::value::Dict::new();
-                for (key, value) in tbl.iter() {
-                    dict.insert(key.clone(), Self::toml_to_figment_value(value));
-                }
-                FigmentValue::from(dict)
-            }
-            TomlValue::Datetime(_) => todo!("not implemented yet!"),
-        }
-    }
-
-    fn try_parse_value(value: &str) -> FigmentValue {
-        if value.starts_with('[') && value.ends_with(']') {
-            let value = value.trim_start_matches('[').trim_end_matches(']');
-            let values: Vec<FigmentValue> = value.split(',').map(Self::try_parse_value).collect();
-            return FigmentValue::from(values);
-        }
-        if value == "true" {
-            return FigmentValue::from(true);
-        }
-        if value == "false" {
-            return FigmentValue::from(false);
-        }
-        // Try u64 first for most numeric values (ports, byte sizes, etc.)
-        if let Ok(uint_val) = value.parse::<u64>() {
-            return FigmentValue::from(uint_val);
-        }
-        // Fall back to i64 for signed integers
-        if let Ok(int_val) = value.parse::<i64>() {
-            return FigmentValue::from(int_val);
-        }
-        if let Ok(float_val) = value.parse::<f64>() {
-            return FigmentValue::from(float_val);
-        }
-        FigmentValue::from(value)
-    }
-}
-
-impl Provider for CustomEnvProvider {
-    fn metadata(&self) -> Metadata {
-        Metadata::named("iggy-server config")
-    }
-
-    fn data(&self) -> Result<FigmentMap<Profile, Dict>, Error> {
-        let default_config = toml::to_string(&ServerConfig::default())
-            .expect("Cannot serialize default ServerConfig. Something's terribly wrong.");
-        let toml_value: TomlValue = toml::from_str(&default_config).unwrap();
-        let mut source_dict = Dict::new();
-        if let TomlValue::Table(table) = toml_value {
-            Self::walk_toml_table_to_dict("", table, &mut source_dict);
-        }
-
-        let mut new_dict = Dict::new();
-        for (key, mut value) in env::vars() {
-            let env_key = key.to_uppercase();
-            if !env_key.starts_with(self.prefix.as_str()) {
-                continue;
-            }
-            let keys: Vec<String> = env_key[self.prefix.len()..]
-                .split('_')
-                .map(|k| k.to_lowercase())
-                .collect();
-            let env_var_value = Self::try_parse_value(&value);
-            if SECRET_KEYS.contains(&env_key.as_str()) {
-                value = "******".to_string();
-            }
-
-            println!("{env_key} value changed to: {value} from environment variable");
-            Self::insert_overridden_values_from_env(
-                &source_dict,
-                &mut new_dict,
-                keys.clone(),
-                env_var_value.clone(),
-            );
-        }
-        let mut data = FigmentMap::new();
-        data.insert(Profile::default(), new_dict);
-
-        Ok(data)
-    }
-}
-
-pub fn resolve(config_provider_type: &str) -> Result<ConfigProviderKind, ConfigError> {
-    match config_provider_type {
-        DEFAULT_CONFIG_PROVIDER => {
-            let path =
-                env::var("IGGY_CONFIG_PATH").unwrap_or_else(|_| DEFAULT_CONFIG_PATH.to_string());
-            Ok(ConfigProviderKind::File(FileConfigProvider::new(path)))
-        }
-        _ => Err(ConfigError::InvalidConfigurationProvider {
-            provider_type: config_provider_type.to_string(),
-        }),
-    }
-}
-
-/// This does exactly the same as Figment does internally.
-fn file_exists<P: AsRef<Path>>(path: P) -> bool {
-    let path = path.as_ref();
-
-    if path.is_absolute() {
-        return path.is_file();
-    }
-
-    let cwd = match std::env::current_dir() {
-        Ok(dir) => dir,
-        Err(_) => return false,
-    };
-
-    let mut current_dir = cwd.as_path();
-    loop {
-        let file_path = current_dir.join(path);
-        if file_path.is_file() {
-            return true;
-        }
-
-        current_dir = match current_dir.parent() {
-            Some(parent) => parent,
-            None => return false,
-        };
-    }
-}
-
-impl ConfigProvider for FileConfigProvider {
-    async fn load_config(&self) -> Result<ServerConfig, ConfigError> {
-        println!("Loading config from path: '{}'...", self.path);
-
-        // Include the default configuration from server.toml
-        let embedded_default_config = Toml::string(include_str!("../../../configs/server.toml"));
-
-        // Start with the default configuration
-        let mut config_builder = Figment::new().merge(embedded_default_config);
-
-        // If the server.toml file exists, merge it into the configuration
-        if file_exists(&self.path) {
-            println!("Found configuration file at path: '{}'.", self.path);
-            config_builder = config_builder.merge(Toml::file(&self.path));
-        } else {
-            println!(
-                "Configuration file not found at path: '{}'. Using default configuration from embedded server.toml.",
-                self.path
-            );
-        }
-
-        // Merge environment variables into the configuration
-        config_builder = config_builder.merge(CustomEnvProvider::new("IGGY_"));
-
-        // Finally, attempt to extract the final configuration
-        let config_result: Result<ServerConfig, figment::Error> = config_builder.extract();
-
-        match config_result {
-            Ok(config) => {
-                println!("Config loaded successfully.");
-                println!("Using Config: {config}");
-                Ok(config)
-            }
-            Err(e) => {
-                println!("Failed to load config: {e}");
-                Err(ConfigError::CannotLoadConfiguration)
-            }
-        }
-    }
-}
diff --git a/core/server/src/configs/mod.rs b/core/server/src/configs/mod.rs
index ff8003afa..8bee32c44 100644
--- a/core/server/src/configs/mod.rs
+++ b/core/server/src/configs/mod.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -18,7 +19,6 @@
 
 pub mod cache_indexes;
 pub mod cluster;
-pub mod config_provider;
 pub mod defaults;
 pub mod displays;
 pub mod http;
diff --git a/core/server/src/configs/server.rs b/core/server/src/configs/server.rs
index 446fe78d3..927386aef 100644
--- a/core/server/src/configs/server.rs
+++ b/core/server/src/configs/server.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -15,26 +16,39 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
+use crate::IGGY_ROOT_PASSWORD_ENV;
 use crate::configs::COMPONENT;
 use crate::configs::cluster::ClusterConfig;
-use crate::configs::config_provider::ConfigProviderKind;
 use crate::configs::http::HttpConfig;
 use crate::configs::quic::QuicConfig;
 use crate::configs::system::SystemConfig;
 use crate::configs::tcp::TcpConfig;
 use crate::configs::websocket::WebSocketConfig;
-use crate::server_error::ConfigError;
+use crate::server_error::ConfigurationError;
 use derive_more::Display;
 use err_trail::ErrContext;
-use iggy_common::IggyDuration;
-use iggy_common::Validatable;
+use figment::providers::{Format, Toml};
+use figment::value::Dict;
+use figment::{Metadata, Profile, Provider};
+use iggy_common::{ConfigProvider, Validatable};
+use iggy_common::{CustomEnvProvider, FileConfigProvider, IggyDuration};
 use serde::{Deserialize, Serialize};
 use serde_with::DisplayFromStr;
 use serde_with::serde_as;
+use std::env;
 use std::str::FromStr;
 use std::sync::Arc;
 
+const DEFAULT_CONFIG_PATH: &str = "configs/server.toml";
+const SECRET_KEYS: [&str; 6] = [
+    IGGY_ROOT_PASSWORD_ENV,
+    "IGGY_DATA_MAINTENANCE_ARCHIVER_S3_KEY_SECRET",
+    "IGGY_HTTP_JWT_ENCODING_SECRET",
+    "IGGY_HTTP_JWT_DECODING_SECRET",
+    "IGGY_TCP_TLS_PASSWORD",
+    "IGGY_SYSTEM_ENCRYPTION_KEY",
+];
+
 #[derive(Debug, Deserialize, Serialize, Clone)]
 pub struct ServerConfig {
     pub data_maintenance: DataMaintenanceConfig,
@@ -136,13 +150,52 @@ impl FromStr for TelemetryTransport {
 }
 
 impl ServerConfig {
-    pub async fn load(config_provider: &ConfigProviderKind) -> Result<ServerConfig, ConfigError> {
-        let server_config = config_provider.load_config().await.with_error(|error| {
-            format!("{COMPONENT} (error: {error}) - failed to load config provider config")
-        })?;
+    pub async fn load() -> Result<ServerConfig, ConfigurationError> {
+        let config_path =
+            env::var("IGGY_CONFIG_PATH").unwrap_or_else(|_| DEFAULT_CONFIG_PATH.to_string());
+        let config_provider = ServerConfig::config_provider(&config_path);
+        let server_config: ServerConfig =
+            config_provider.load_config().await.with_error(|error| {
+                format!("{COMPONENT} (error: {error}) - failed to load config provider config")
+            })?;
         server_config.validate().with_error(|error| {
             format!("{COMPONENT} (error: {error}) - failed to validate server config")
         })?;
         Ok(server_config)
     }
+
+    pub fn config_provider(config_path: &str) -> FileConfigProvider<ServerConfigEnvProvider> {
+        let default_config = Toml::string(include_str!("../../../configs/server.toml"));
+        FileConfigProvider::new(
+            config_path.to_string(),
+            ServerConfigEnvProvider::default(),
+            true,
+            Some(default_config),
+        )
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct ServerConfigEnvProvider {
+    provider: CustomEnvProvider<ServerConfig>,
+}
+
+impl Default for ServerConfigEnvProvider {
+    fn default() -> Self {
+        Self {
+            provider: CustomEnvProvider::new("IGGY_", &SECRET_KEYS),
+        }
+    }
+}
+
+impl Provider for ServerConfigEnvProvider {
+    fn metadata(&self) -> Metadata {
+        Metadata::named("iggy-server config")
+    }
+
+    fn data(&self) -> Result<figment::value::Map<Profile, Dict>, figment::Error> {
+        self.provider.deserialize().map_err(|_| {
+            figment::Error::from("Cannot deserialize environment variables for server config")
+        })
+    }
 }
diff --git a/core/server/src/configs/validators.rs b/core/server/src/configs/validators.rs
index 83aff5858..460eb71d8 100644
--- a/core/server/src/configs/validators.rs
+++ b/core/server/src/configs/validators.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -25,18 +26,17 @@ use super::system::{CompressionConfig, MemoryPoolConfig, PartitionConfig};
 use crate::configs::COMPONENT;
 use crate::configs::server::{PersonalAccessTokenConfig, ServerConfig};
 use crate::configs::system::SegmentConfig;
-use crate::server_error::ConfigError;
 use crate::streaming::segments::*;
 use err_trail::ErrContext;
-use iggy_common::CompressionAlgorithm;
 use iggy_common::IggyExpiry;
 use iggy_common::MaxTopicSize;
 use iggy_common::Validatable;
+use iggy_common::{CompressionAlgorithm, ConfigurationError};
 use std::thread::available_parallelism;
 use tracing::error;
 
-impl Validatable<ConfigError> for ServerConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for ServerConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         self.system.memory_pool.validate().with_error(|error| {
             format!("{COMPONENT} (error: {error}) - failed to validate memory pool config")
         })?;
@@ -67,29 +67,29 @@ impl Validatable<ConfigError> for ServerConfig {
         let topic_size = match self.system.topic.max_size {
             MaxTopicSize::Custom(size) => Ok(size.as_bytes_u64()),
             MaxTopicSize::Unlimited => Ok(u64::MAX),
-            MaxTopicSize::ServerDefault => Err(ConfigError::InvalidConfiguration),
+            MaxTopicSize::ServerDefault => Err(ConfigurationError::InvalidConfigurationValue),
         }?;
 
         if let IggyExpiry::ServerDefault = self.system.segment.message_expiry {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.http.enabled
             && let IggyExpiry::ServerDefault = self.http.jwt.access_token_expiry
         {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if topic_size < self.system.segment.size.as_bytes_u64() {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for CompressionConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for CompressionConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         let compression_alg = &self.default_algorithm;
         if *compression_alg != CompressionAlgorithm::None {
             // TODO(numinex): Change this message once server side compression is fully developed.
@@ -102,36 +102,36 @@ impl Validatable<ConfigError> for CompressionConfig {
     }
 }
 
-impl Validatable<ConfigError> for TelemetryConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for TelemetryConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if !self.enabled {
             return Ok(());
         }
 
         if self.service_name.trim().is_empty() {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.logs.endpoint.is_empty() {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.traces.endpoint.is_empty() {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for PartitionConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for PartitionConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if self.messages_required_to_save < 32 {
             eprintln!(
                 "Configured system.partition.messages_required_to_save {} is less than minimum {}",
                 self.messages_required_to_save, 32
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if !self.messages_required_to_save.is_multiple_of(32) {
@@ -139,7 +139,7 @@ impl Validatable<ConfigError> for PartitionConfig {
                 "Configured system.partition.messages_required_to_save {} is not a multiple of 32",
                 self.messages_required_to_save
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.size_of_messages_required_to_save < 512 {
@@ -147,7 +147,7 @@ impl Validatable<ConfigError> for PartitionConfig {
                 "Configured system.partition.size_of_messages_required_to_save {} is less than minimum {}",
                 self.size_of_messages_required_to_save, 512
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if !self
@@ -159,22 +159,22 @@ impl Validatable<ConfigError> for PartitionConfig {
                 "Configured system.partition.size_of_messages_required_to_save {} is not a multiple of 512 B",
                 self.size_of_messages_required_to_save
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for SegmentConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for SegmentConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if self.size > SEGMENT_MAX_SIZE_BYTES {
             eprintln!(
                 "Configured system.segment.size {} B is greater than maximum {} B",
                 self.size.as_bytes_u64(),
                 SEGMENT_MAX_SIZE_BYTES
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if !self.size.as_bytes_u64().is_multiple_of(512) {
@@ -182,25 +182,25 @@ impl Validatable<ConfigError> for SegmentConfig {
                 "Configured system.segment.size {} B is not a multiple of 512 B",
                 self.size.as_bytes_u64()
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for MessageSaverConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for MessageSaverConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if self.enabled && self.interval.is_zero() {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for DataMaintenanceConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for DataMaintenanceConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         self.messages.validate().with_error(|error| {
             format!("{COMPONENT} (error: {error}) - failed to validate messages maintenance config")
         })?;
@@ -208,37 +208,37 @@ impl Validatable<ConfigError> for DataMaintenanceConfig {
     }
 }
 
-impl Validatable<ConfigError> for MessagesMaintenanceConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for MessagesMaintenanceConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if self.cleaner_enabled && self.interval.is_zero() {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for PersonalAccessTokenConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for PersonalAccessTokenConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if self.max_tokens_per_user == 0 {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.cleaner.enabled && self.cleaner.interval.is_zero() {
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for MemoryPoolConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for MemoryPoolConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if self.enabled && self.size == 0 {
             error!(
                 "Configured system.memory_pool.enabled is true and system.memory_pool.size is 0"
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         const MIN_POOL_SIZE: u64 = 512 * 1024 * 1024; // 512 MiB
@@ -253,7 +253,7 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
                 MIN_POOL_SIZE,
                 MIN_POOL_SIZE / (1024 * 1024),
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.enabled && !self.size.as_bytes_u64().is_multiple_of(DEFAULT_PAGE_SIZE) {
@@ -262,7 +262,7 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
                 self.size.as_bytes_u64(),
                 DEFAULT_PAGE_SIZE
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.enabled && self.bucket_capacity < MIN_BUCKET_CAPACITY {
@@ -270,7 +270,7 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
                 "Configured system.memory_pool.buffers {} is less than minimum {}",
                 self.bucket_capacity, MIN_BUCKET_CAPACITY
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         if self.enabled && !self.bucket_capacity.is_power_of_two() {
@@ -278,15 +278,15 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
                 "Configured system.memory_pool.buffers {} is not a power of 2",
                 self.bucket_capacity
             );
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         Ok(())
     }
 }
 
-impl Validatable<ConfigError> for ShardingConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for ShardingConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         let available_cpus = available_parallelism()
             .expect("Failed to get number of CPU cores")
             .get();
@@ -296,13 +296,13 @@ impl Validatable<ConfigError> for ShardingConfig {
             CpuAllocation::Count(count) => {
                 if *count == 0 {
                     eprintln!("Invalid sharding configuration: cpu_allocation count cannot be 0");
-                    return Err(ConfigError::InvalidConfiguration);
+                    return Err(ConfigurationError::InvalidConfigurationValue);
                 }
                 if *count > available_cpus {
                     eprintln!(
                         "Invalid sharding configuration: cpu_allocation count {count} exceeds available CPU cores {available_cpus}"
                     );
-                    return Err(ConfigError::InvalidConfiguration);
+                    return Err(ConfigurationError::InvalidConfigurationValue);
                 }
                 Ok(())
             }
@@ -311,13 +311,13 @@ impl Validatable<ConfigError> for ShardingConfig {
                     eprintln!(
                         "Invalid sharding configuration: cpu_allocation range {start}..{end} is invalid (start must be less than end)"
                     );
-                    return Err(ConfigError::InvalidConfiguration);
+                    return Err(ConfigurationError::InvalidConfigurationValue);
                 }
                 if *end > available_cpus {
                     eprintln!(
                         "Invalid sharding configuration: cpu_allocation range {start}..{end} exceeds available CPU cores (max: {available_cpus})"
                     );
-                    return Err(ConfigError::InvalidConfiguration);
+                    return Err(ConfigurationError::InvalidConfigurationValue);
                 }
                 Ok(())
             }
@@ -325,8 +325,8 @@ impl Validatable<ConfigError> for ShardingConfig {
     }
 }
 
-impl Validatable<ConfigError> for ClusterConfig {
-    fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for ClusterConfig {
+    fn validate(&self) -> Result<(), ConfigurationError> {
         if !self.enabled {
             return Ok(());
         }
@@ -334,13 +334,13 @@ impl Validatable<ConfigError> for ClusterConfig {
         // Validate cluster name is not empty
         if self.name.trim().is_empty() {
             eprintln!("Invalid cluster configuration: cluster name cannot be empty");
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         // Validate current node name is not empty
         if self.node.current.name.trim().is_empty() {
             eprintln!("Invalid cluster configuration: current node name cannot be empty");
-            return Err(ConfigError::InvalidConfiguration);
+            return Err(ConfigurationError::InvalidConfigurationValue);
         }
 
         // Check for duplicate node names among other nodes
@@ -353,7 +353,7 @@ impl Validatable<ConfigError> for ClusterConfig {
                     "Invalid cluster configuration: duplicate node name '{}' found",
                     node.name
                 );
-                return Err(ConfigError::InvalidConfiguration);
+                return Err(ConfigurationError::InvalidConfigurationValue);
             }
         }
 
@@ -363,7 +363,7 @@ impl Validatable<ConfigError> for ClusterConfig {
             // Validate node name is not empty
             if node.name.trim().is_empty() {
                 eprintln!("Invalid cluster configuration: node name cannot be empty");
-                return Err(ConfigError::InvalidConfiguration);
+                return Err(ConfigurationError::InvalidConfigurationValue);
             }
 
             // Validate IP is not empty
@@ -372,7 +372,7 @@ impl Validatable<ConfigError> for ClusterConfig {
                     "Invalid cluster configuration: IP cannot be empty for node '{}'",
                     node.name
                 );
-                return Err(ConfigError::InvalidConfiguration);
+                return Err(ConfigurationError::InvalidConfigurationValue);
             }
 
             // Validate transport ports if provided
@@ -390,7 +390,7 @@ impl Validatable<ConfigError> for ClusterConfig {
                             "Invalid cluster configuration: {} port cannot be 0 for node '{}'",
                             name, node.name
                         );
-                        return Err(ConfigError::InvalidConfiguration);
+                        return Err(ConfigurationError::InvalidConfigurationValue);
                     }
 
                     // Check for port conflicts across nodes on the same IP
@@ -400,7 +400,7 @@ impl Validatable<ConfigError> for ClusterConfig {
                             "Invalid cluster configuration: port conflict - {}:{} is already used",
                             node.ip, port
                         );
-                        return Err(ConfigError::InvalidConfiguration);
+                        return Err(ConfigurationError::InvalidConfigurationValue);
                     }
                 }
             }
diff --git a/core/server/src/main.rs b/core/server/src/main.rs
index 7283d40df..44a2357d3 100644
--- a/core/server/src/main.rs
+++ b/core/server/src/main.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -28,7 +29,6 @@ use server::bootstrap::{
     create_directories, create_shard_connections, create_shard_executor, load_config, load_streams,
     load_users, resolve_persister, update_system_info,
 };
-use server::configs::config_provider::{self};
 use server::configs::sharding::CpuAllocation;
 use server::diagnostics::{print_io_uring_permission_info, print_locked_memory_limit_info};
 use server::io::fs_utils;
@@ -99,8 +99,7 @@ fn main() -> Result<(), ServerError> {
         // FIRST DISCRETE LOADING STEP.
         // Load config and create directories.
         // Remove `local_data` directory if run with `--fresh` flag.
-        let config_provider = config_provider::resolve(&args.config_provider)?;
-        let config = load_config(&config_provider).await.with_error(|error| {
+        let config = load_config().await.with_error(|error| {
             format!("{COMPONENT} (error: {error}) - failed to load config during bootstrap")
         })?;
         if args.fresh {
diff --git a/core/server/src/server_error.rs b/core/server/src/server_error.rs
index 02822a1d0..4cc01f414 100644
--- a/core/server/src/server_error.rs
+++ b/core/server/src/server_error.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
@@ -22,7 +23,7 @@ use std::array::TryFromSliceError;
 use std::io;
 
 error_set!(
-    ServerError := ConfigError || ArchiverError || ConnectionError || LogError || CompatError || QuicError
+    ServerError := ConfigurationError || ArchiverError || ConnectionError || LogError || CompatError || QuicError
 
     IoError := {
         #[display("IO error")]
@@ -35,18 +36,8 @@ error_set!(
         ReadToEndError(ReadError)
     }
 
-    ConfigError := {
-        #[display("Invalid configuration provider: {}", provider_type)]
-        InvalidConfigurationProvider { provider_type: String },
-
-        #[display("Cannot load configuration")]
-        CannotLoadConfiguration,
-
-        #[display("Invalid configuration")]
-        InvalidConfiguration,
-
-        #[display("Cache config validation failure")]
-        CacheConfigValidationFailure,
+    ConfigurationError := {
+        ConfigurationError(iggy_common::ConfigurationError),
     }
 
     ArchiverError := {