diff --git a/proto/encore/runtime/v1/infra.proto b/proto/encore/runtime/v1/infra.proto index d1da987f72..c50d89536b 100644 --- a/proto/encore/runtime/v1/infra.proto +++ b/proto/encore/runtime/v1/infra.proto @@ -332,6 +332,13 @@ message BucketCluster { // as opposed to resolving using AWS's default credential chain. optional string access_key_id = 3; optional SecretData secret_access_key = 4; + + // If true, use path-style bucket addressing instead of virtual-hosted-style. + // When enabled, the bucket name will be placed in the request path + // (https://endpoint/bucket/key) instead of as a subdomain + // (https://bucket.endpoint/key). This is useful for S3-compatible providers + // like MinIO or local testing endpoints. + bool force_path_style = 5; } message GCS { diff --git a/runtimes/core/src/infracfg.rs b/runtimes/core/src/infracfg.rs index f6eb296b05..8221c783e9 100644 --- a/runtimes/core/src/infracfg.rs +++ b/runtimes/core/src/infracfg.rs @@ -50,6 +50,8 @@ pub struct S3 { pub endpoint: Option, pub access_key_id: Option, pub secret_access_key: Option, + #[serde(default)] + pub force_path_style: bool, pub buckets: HashMap, } @@ -475,6 +477,7 @@ pub fn map_infra_to_runtime(infra: InfraConfig) -> RuntimeConfig { .secret_access_key .as_ref() .map(map_env_string_to_secret_data), + force_path_style: s3.force_path_style, }, )), buckets: s3 diff --git a/runtimes/core/src/objects/s3/mod.rs b/runtimes/core/src/objects/s3/mod.rs index c31dc3bbdd..b0913cbcdf 100644 --- a/runtimes/core/src/objects/s3/mod.rs +++ b/runtimes/core/src/objects/s3/mod.rs @@ -78,7 +78,12 @@ impl LazyS3Client { } let cfg = builder.load().await; - Arc::new(s3::Client::new(&cfg)) + let mut s3conf = aws_sdk_s3::config::Builder::from(&cfg); + if self.cfg.force_path_style { + s3conf = s3conf.force_path_style(true); + } + let s3client = s3::Client::from_conf(s3conf.build()); + Arc::new(s3client) }) .await }