Richard Hatherall By Richard Hatherall 5 min read aws-sdk aws s3 delphi tutorial

Pointing the AWS SDK for Delphi at S3-compatible storage

The AWS SDK for Delphi speaks the S3 protocol, not just Amazon S3. Set one property and the same client talks to MinIO, Cloudflare R2, Backblaze B2, or DigitalOcean Spaces.

Pointing the AWS SDK for Delphi at S3-compatible storage
Contents
  1. The setting that does it
  2. Where to put the endpoint
  3. Trying it against a real endpoint
  4. What carries over, and what does not
  5. Mixing providers
  6. What's next

The AWS SDK for Delphi is named for AWS, but much of it is built on protocols rather than on Amazon's servers specifically. S3 is the clearest example. "S3" is an HTTP API that Amazon S3 implements, and so do a number of other storage providers: MinIO, Cloudflare R2, Backblaze B2 and DigitalOcean Spaces all speak it. Point the SDK's S3 client at one of them and your existing Delphi code keeps working.

This post starts a series on doing exactly that. It covers the one setting that redirects the client, the places you can set it, and which parts of your code stay portable across providers. The posts after this one each take a single provider and walk through its specifics.

The setting that does it

By default, an S3 client talks to Amazon S3:

uses
  AWS.S3;

var
  Client: IS3Client;
begin
  Client := TS3Client.Create;

The client resolves a region from your configuration and sends requests to the matching Amazon S3 endpoint. To send them somewhere else, set an endpoint URL on an options object and pass it to the constructor:

uses
  AWS.S3;

var
  Options: IS3Options;
  Client: IS3Client;
begin
  Options := TS3Options.Create as IS3Options;
  Options.EndpointUrl := 'http://localhost:9000';
  Client := TS3Client.Create(Options);

That is the whole mechanism. Everything else in the SDK behaves as it did before. The options object is the same one you would use to set a region or profile in code; EndpointUrl is one more property on it.

Where to put the endpoint

Hardcoding the URL is fine for a quick test, but you usually do not want the endpoint baked into the binary. The SDK reads it from the same places it reads everything else, so you can keep it out of code.

In the shared config file. ~/.aws/config takes an endpoint_url setting, the same line the AWS CLI reads, so configuring it once covers both:

# ~/.aws/config
[default]
region = us-east-1
endpoint_url = http://localhost:9000

At profile level, endpoint_url applies to every service, which is rarely what you want. Nest it under a service key to scope it to S3:

# ~/.aws/config
[default]
region = us-east-1
s3 =
  endpoint_url = http://localhost:9000

That is usually the right form: S3 requests go to the endpoint you chose, everything else keeps resolving against AWS.

Windows INI files are flat: sections hold a plain list of key = value pairs. The AWS config format allows nesting; the s3 = block here is a nested subsection. Endpoint URL, region, addressing style, and other S3-specific keys all belong in that block if they only apply to S3.

If your app only ever uses one S3-compatible provider, the default profile is a fine home. Once an app mixes AWS S3 with a third-party provider in the same codebase, use separate profiles:

# ~/.aws/config
[default]
region = us-east-1

[profile minio]
region = us-east-1
s3 =
  endpoint_url = http://localhost:9000

Set the profile per client (on its options object or via AWS_PROFILE) and each one goes to the right endpoint.

As an environment variable. AWS_ENDPOINT_URL overrides the endpoint for a single run without touching files, which suits CI and container deployments:

AWS_ENDPOINT_URL=http://localhost:9000 MyApp.exe

The service-scoped form is AWS_ENDPOINT_URL_S3, mirroring the nested s3 = block above.

In code. Use the options object shown earlier when the endpoint belongs to the application's logic rather than its environment.

This is the shape from the credentials post: a setting with a fixed precedence order, configurable wherever fits the environment the code runs in.

Trying it against a real endpoint

MinIO is a common way to run an S3-compatible endpoint locally: a single binary, or one docker run, that serves the S3 API. That suits a first try and tests:

docker run -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=minioadmin \
  -e MINIO_ROOT_PASSWORD=minioadmin \
  minio/minio server /data --console-address ":9001"

Create a bucket from the console at http://localhost:9001, give the SDK credentials to match (an AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY pair set to the values above), and the upload-and-download code is identical to the Amazon S3 version:

uses
  AWS.S3;

var
  Options: IS3Options;
  Client: IS3Client;
  Obj: IS3Object;
begin
  Options := TS3Options.Create as IS3Options;
  Options.EndpointUrl := 'http://localhost:9000';
  Client := TS3Client.Create(Options);

  Obj := TS3Object.Create('my-bucket', 'report.pdf', Client);
  Obj.UploadFile('C:\Reports\quarterly.pdf');
  Obj.DownloadFile('C:\Downloads\quarterly.pdf');
end;

The only line that knows you are not talking to AWS is the one that sets EndpointUrl.

What carries over, and what does not

Credentials work the same way everywhere. The provider chain from the credentials post is unchanged: the SDK still reads an access key and secret from the environment, a profile, or a role. S3-compatible providers issue their own access-key and secret pairs that drop into the same slots. Nothing about credential handling changes when you change the endpoint.

The core object operations carry over too. Creating buckets, putting and getting objects, listing, copying, deleting, and the file uploader's automatic multipart handling are the parts of the S3 API every implementation supports, and they behave the same against any of them.

What varies is at the edges:

  • Regions. Every provider expects a region value even when it does not mean what it means on AWS. Some want a real region, some a fixed token like auto, some ignore it but still require it to be set.
  • Feature coverage. Versioning, lifecycle rules, presigned-URL behaviour, storage classes and the rest are implemented to differing degrees. The object basics are mostly safe to assume, though some providers still implement only older versions of otherwise-common operations. Anything past the basics is worth checking against the provider.

Each provider gets its own post. The settings that matter, and the ones that bite, are specific to each one.

Mixing providers

Delphi applications routinely use both. Self-hosted MinIO for data that has to stay on-premises, R2 where egress costs dominate, Amazon S3 for everything already there. The SDK treats them all as the same API, so mixing them is a configuration choice rather than a code change.

What's next

Next in the series: Cloudflare R2 from Delphi. Its S3-compatible API, where it differs (region handling and addressing in particular), and a worked example end to end. Each provider after that follows the same shape.

If you have not set up credentials yet, start with Credentials for the AWS SDK for Delphi. The endpoint is the only thing this series adds on top.