{"id":3903,"date":"2025-08-25T10:00:00","date_gmt":"2025-08-25T14:00:00","guid":{"rendered":"https:\/\/www.mymiller.name\/wordpress\/?p=3903"},"modified":"2025-08-24T09:49:38","modified_gmt":"2025-08-24T13:49:38","slug":"the-s3-local-dev-trick-using-minio-to-simplify-cloud-native-developmen","status":"publish","type":"post","link":"https:\/\/www.mymiller.name\/wordpress\/docker\/the-s3-local-dev-trick-using-minio-to-simplify-cloud-native-developmen\/","title":{"rendered":"The S3 Local Dev Trick: Using MinIO to Simplify Cloud-Native Developmen"},"content":{"rendered":"\n<p>As a software architect building cloud-native solutions, you know that working with cloud services like AWS S3 can be a bit tricky in a local development environment. You don&#8217;t want to constantly connect to a remote bucket, and setting up complex local testing environments can be a pain.<\/p>\n\n\n\n<p>But what if you could have a fully functional, S3-compatible object storage service running right on your machine?<\/p>\n\n\n\n<p>Meet <strong>MinIO<\/strong>. It&#8217;s an open-source, high-performance object storage server that is completely compatible with the Amazon S3 API. This means you can develop your Spring Boot applications locally, pointing them to MinIO, and then seamlessly switch to AWS S3 in production with zero code changes.<\/p>\n\n\n\n<p>This article will walk you through the entire process, from setting up MinIO with Docker Compose to connecting your Spring Boot application using the AWS SDK for Java.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why MinIO for Local Development?<\/h2>\n\n\n\n<p>MinIO&#8217;s core strength is its adherence to the S3 API. This &#8220;S3-compatible&#8221; nature is what makes it so powerful. It also has a number of features that make it perfect for developers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>High Performance:<\/strong> Optimized for AI\/ML and analytics workloads, it&#8217;s fast and efficient.<\/li>\n\n\n\n<li><strong>Scalability:<\/strong> While we&#8217;ll use a single-node setup for this guide, MinIO is designed to scale horizontally to petabytes.<\/li>\n\n\n\n<li><strong>Lightweight:<\/strong> It&#8217;s designed to run anywhere, from your local machine to a large-scale Kubernetes cluster.<\/li>\n\n\n\n<li><strong>Data Persistence:<\/strong> We can configure it to persist data even after the container is stopped.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Setting up MinIO with Docker Compose<\/h2>\n\n\n\n<p>Docker Compose is the perfect tool for this because it allows us to define and run a multi-container application with a single command.<\/p>\n\n\n\n<p>Create a file named <code>docker-compose.yml<\/code> in your project root and add the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>version: '3.8'\n\nservices:\n  minio:\n    image: minio\/minio:latest\n    container_name: minio_server\n    # Map port 9000 (MinIO API) and 9001 (MinIO Console\/UI)\n    ports:\n      - \"9000:9000\"\n      - \"9001:9001\"\n    # Set environment variables for the root user credentials\n    environment:\n      MINIO_ROOT_USER: minioadmin\n      MINIO_ROOT_PASSWORD: minioadmin\n    # Command to start the MinIO server and specify the console port\n    command: server \/data --console-address \":9001\"\n    # Volume to persist data across container restarts\n    volumes:\n      - minio_data:\/data\n    healthcheck:\n      test: &#91;\"CMD\", \"curl\", \"-f\", \"http:\/\/localhost:9000\/minio\/health\/live\"]\n      interval: 30s\n      timeout: 20s\n      retries: 3\n\n  mc:\n    image: minio\/mc:latest\n    container_name: minio_client\n    depends_on:\n      - minio\n    entrypoint: \/bin\/sh\n    command: -c \"\n      \/usr\/bin\/mc alias set local http:\/\/minio:9000 minioadmin minioadmin;\n      \/usr\/bin\/mc mb local\/my-spring-bucket;\n      tail -f \/dev\/null\n    \"\n\nvolumes:\n  minio_data: {}\n<\/code><\/pre>\n\n\n\n<p>This file defines two services:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>minio<\/code><\/strong>: The core MinIO server that stores our data. We&#8217;ve exposed ports <code>9000<\/code> (for the S3 API) and <code>9001<\/code> (for the web console).<\/li>\n\n\n\n<li><strong><code>mc<\/code><\/strong>: An optional but handy MinIO Client container that automatically creates a bucket named <code>my-spring-bucket<\/code> for us on startup.<\/li>\n<\/ul>\n\n\n\n<p>To start the containers, open a terminal in the same directory as the file and run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose up -d\n<\/code><\/pre>\n\n\n\n<p>You can now access the MinIO web console at <strong><code>http:\/\/localhost:9001<\/code><\/strong> using the credentials <code>minioadmin<\/code>\/<code>minioadmin<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Connecting Your Spring Boot Application<\/h2>\n\n\n\n<p>Now that our local MinIO server is running, we&#8217;ll connect our Spring Boot application to it using the official AWS SDK for Java v2.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Add Gradle Dependencies<\/h3>\n\n\n\n<p>First, add the necessary dependencies to your <code>build.gradle<\/code> file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ build.gradle\n\ndependencies {\n    \/\/ Use the BOM for managing AWS SDK versions\n    implementation platform('software.amazon.awssdk:bom:2.20.108')\n    implementation 'software.amazon.awssdk:s3'\n\n    \/\/ Spring Boot and other standard dependencies\n    implementation 'org.springframework.boot:spring-boot-starter'\n    \/\/ ...\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Configure Your Application<\/h3>\n\n\n\n<p>Next, configure the connection properties in your <code>application.yml<\/code> file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># src\/main\/resources\/application.yml\n\nminio:\n  endpoint: http:\/\/localhost:9000\n  access-key: minioadmin\n  secret-key: minioadmin\n  bucket-name: my-spring-bucket\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create the S3 Client Configuration<\/h3>\n\n\n\n<p>Because the AWS SDK is designed for AWS, you need a configuration bean to override the default settings and point it to your local MinIO instance. This is a crucial step!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/main\/java\/com\/example\/config\/MinioClientConfig.java\npackage com.example.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.beans.factory.annotation.Value;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.services.s3.S3Client;\nimport java.net.URI;\n\n@Configuration\npublic class MinioClientConfig {\n\n    @Value(\"${minio.endpoint}\")\n    private String endpoint;\n\n    @Value(\"${minio.access-key}\")\n    private String accessKey;\n\n    @Value(\"${minio.secret-key}\")\n    private String secretKey;\n\n    @Bean\n    public S3Client s3Client() {\n        return S3Client.builder()\n                \/\/ 1. Specify the local MinIO endpoint URL\n                .endpointOverride(URI.create(endpoint))\n\n                \/\/ 2. Set static credentials from application.yml\n                .credentialsProvider(StaticCredentialsProvider.create(\n                        AwsBasicCredentials.create(accessKey, secretKey)\n                ))\n\n                \/\/ 3. MinIO requires Path Style Access for a local setup\n                .forcePathStyle(true)\n\n                \/\/ 4. Region is required by the SDK but can be arbitrary for MinIO\n                .region(Region.of(\"us-east-1\"))\n                .build();\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>The <code>forcePathStyle(true)<\/code> setting is especially important as it ensures the bucket name is part of the URL path, which is how MinIO handles requests.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Implement the Service Class<\/h3>\n\n\n\n<p>Finally, create a service that uses the <code>S3Client<\/code> to interact with your MinIO storage.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/main\/java\/com\/example\/service\/MinioService.java\npackage com.example.service;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport software.amazon.awssdk.core.sync.RequestBody;\nimport software.amazon.awssdk.services.s3.S3Client;\nimport software.amazon.awssdk.services.s3.model.PutObjectRequest;\nimport software.amazon.awssdk.services.s3.model.GetObjectRequest;\nimport software.amazon.awssdk.services.s3.model.DeleteObjectRequest;\nimport java.io.InputStream;\n\n@Service\npublic class MinioService {\n\n    private final S3Client s3Client;\n\n    @Value(\"${minio.bucket-name}\")\n    private String bucketName;\n\n    public MinioService(S3Client s3Client) {\n        this.s3Client = s3Client;\n    }\n\n    \/**\n     * Uploads a file to the MinIO bucket.\n     *\/\n    public void uploadFile(String objectKey, InputStream inputStream, long size, String contentType) {\n        PutObjectRequest putObjectRequest = PutObjectRequest.builder()\n                .bucket(bucketName)\n                .key(objectKey)\n                .contentType(contentType)\n                .contentLength(size)\n                .build();\n\n        s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(inputStream, size));\n        System.out.println(\"Successfully uploaded object: \" + objectKey);\n    }\n\n    \/**\n     * Downloads a file from the MinIO bucket.\n     *\/\n    public InputStream downloadFile(String objectKey) {\n        GetObjectRequest getObjectRequest = GetObjectRequest.builder()\n                .bucket(bucketName)\n                .key(objectKey)\n                .build();\n\n        return s3Client.getObject(getObjectRequest);\n    }\n    \n    \/**\n     * Deletes a file from the MinIO bucket.\n     *\/\n    public void deleteFile(String objectKey) {\n        DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()\n                .bucket(bucketName)\n                .key(objectKey)\n                .build();\n\n        s3Client.deleteObject(deleteObjectRequest);\n        System.out.println(\"Successfully deleted object: \" + objectKey);\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>The methods here use the S3 client to interact with the MinIO server in a familiar way.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>By following these simple steps, you have a powerful and flexible development environment with MinIO. This approach allows you to work offline and test your file storage logic without incurring cloud costs or dealing with network latency. When you&#8217;re ready to deploy, all you have to do is update your <code>application.yml<\/code> with the production AWS S3 credentials, and your application will work seamlessly.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As a software architect building cloud-native solutions, you know that working with cloud services like AWS S3 can be a bit tricky in a local development environment. You don&#8217;t want to constantly connect to a remote bucket, and setting up complex local testing environments can be a pain. But what if you could have a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3905,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[277],"tags":[431,472,471],"series":[],"class_list":["post-3903","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-docker","tag-aws","tag-docker","tag-s3"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/08\/ai-generated-9268117_1280.avif","jetpack-related-posts":[{"id":3744,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_config\/3744\/","url_meta":{"origin":3903,"position":0},"title":"Spring Cloud Config: Choosing the Right Backend Storage","author":"Jeffery Miller","date":"December 23, 2025","format":false,"excerpt":"Spring Cloud Config offers a flexible way to manage your application\u2019s configuration. A crucial step is selecting the right backend to store your configuration data. Let\u2019s explore popular options, their pros and cons, configuration details, and the necessary dependencies for Maven and Gradle. 1. Git Pros: Version Control: Leverage Git\u2019s\u2026","rel":"","context":"In &quot;Spring Config&quot;","block_context":{"text":"Spring Config","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_config\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/04\/woman-8696271_640.jpg?fit=438%2C640&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3897,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_ai\/a-beginners-guide-to-setting-up-ollama-with-docker-compose\/","url_meta":{"origin":3903,"position":1},"title":"A Beginner&#8217;s Guide to Setting Up Ollama with Docker Compose","author":"Jeffery Miller","date":"December 24, 2025","format":false,"excerpt":"Have you ever wanted to run a powerful large language model (LLM) like Llama 3 or Gemma right on your own computer, but you need a consistent and portable setup? That's where using Ollama with Docker and Docker Compose comes in. Docker Compose is a fantastic tool that allows you\u2026","rel":"","context":"In &quot;Spring AI&quot;","block_context":{"text":"Spring AI","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_ai\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/08\/ai-generated-8012676_1280.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/08\/ai-generated-8012676_1280.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/08\/ai-generated-8012676_1280.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/08\/ai-generated-8012676_1280.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/08\/ai-generated-8012676_1280.avif 3x"},"classes":[]},{"id":1542,"url":"https:\/\/www.mymiller.name\/wordpress\/docker\/docker-networking-101\/","url_meta":{"origin":3903,"position":2},"title":"Docker Networking 101","author":"Jeffery Miller","date":"May 12, 2016","format":false,"excerpt":"Docker by default has three networks with it, that containers may use on the host. \u00a0I will try my best to explain them, and how to use them. Bridge The Bridge network, is the 172.17.0.x network that containers use by default. \u00a0This allows them to connect to each other and\u2026","rel":"","context":"In &quot;Docker&quot;","block_context":{"text":"Docker","link":"https:\/\/www.mymiller.name\/wordpress\/category\/docker\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2016\/05\/network-connection-414415_640.jpg?fit=640%2C426&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2016\/05\/network-connection-414415_640.jpg?fit=640%2C426&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2016\/05\/network-connection-414415_640.jpg?fit=640%2C426&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":1537,"url":"https:\/\/www.mymiller.name\/wordpress\/docker\/docker-basics\/","url_meta":{"origin":3903,"position":3},"title":"Docker Basics 101","author":"Jeffery Miller","date":"May 10, 2016","format":false,"excerpt":"Building Docker Image Once you create your Dockerfile for your image, you need to build it. \u00a0You do that by running the following command from the directory that contains the Dockerfile. docker build -t <image_name> . Change \"image_name\" to be the name you want to give this image. \u00a0If you\u2026","rel":"","context":"In &quot;Docker&quot;","block_context":{"text":"Docker","link":"https:\/\/www.mymiller.name\/wordpress\/category\/docker\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3594,"url":"https:\/\/www.mymiller.name\/wordpress\/docker\/fips-jdk-21-image\/","url_meta":{"origin":3903,"position":4},"title":"FIPS JDK 21 Image","author":"Jeffery Miller","date":"July 12, 2024","format":false,"excerpt":"Warning: Use FIPS Instructions at Your Own Risk The provided Dockerfile and instructions are intended to assist in creating a FIPS-compliant environment for your Spring Boot application. However, achieving and maintaining FIPS compliance is a complex process with potential legal and security implications. By following these instructions, you acknowledge and\u2026","rel":"","context":"In &quot;Docker&quot;","block_context":{"text":"Docker","link":"https:\/\/www.mymiller.name\/wordpress\/category\/docker\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_6lwv546lwv546lwv-jpg.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_6lwv546lwv546lwv-jpg.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_6lwv546lwv546lwv-jpg.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_6lwv546lwv546lwv-jpg.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/Gemini_Generated_Image_6lwv546lwv546lwv-jpg.avif 3x"},"classes":[]},{"id":3680,"url":"https:\/\/www.mymiller.name\/wordpress\/aws\/sending-sms-and-mms-messages-with-aws-sns-and-java-spring-integration\/","url_meta":{"origin":3903,"position":5},"title":"Sending SMS and MMS Messages with AWS SNS and Java (Spring Integration)","author":"Jeffery Miller","date":"April 20, 2026","format":false,"excerpt":"AWS Simple Notification Service (SNS) provides a robust platform for sending notifications across various channels, including SMS and MMS. Let\u2019s explore how to implement this functionality using Java within a Spring framework. Prerequisites AWS Account: An active AWS account is necessary. AWS SDK for Java: Make sure you have the\u2026","rel":"","context":"In &quot;AWS&quot;","block_context":{"text":"AWS","link":"https:\/\/www.mymiller.name\/wordpress\/category\/aws\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/09\/smartphone-3152679_1280-jpg.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/09\/smartphone-3152679_1280-jpg.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/09\/smartphone-3152679_1280-jpg.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/09\/smartphone-3152679_1280-jpg.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/09\/smartphone-3152679_1280-jpg.avif 3x"},"classes":[]}],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/3903","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/comments?post=3903"}],"version-history":[{"count":1,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/3903\/revisions"}],"predecessor-version":[{"id":3904,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/3903\/revisions\/3904"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media\/3905"}],"wp:attachment":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media?parent=3903"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/categories?post=3903"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/tags?post=3903"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/series?post=3903"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}