Tabla de Contenidos

07 - S3: almacenamiento de objetos

Las aplicaciones no guardan todo en la base de datos: imágenes, documentos, los ficheros que suben los usuarios. Ese material pertenece al almacenamiento de objetos, cuyo estándar de facto es la API de S3 de AWS: la hablan todas las nubes y los SDK de todas las plataformas. Para el desarrollo se ejecuta esa API en local con MinIO, un servidor compatible desplegado con Docker (tema 02).

1. Almacenamiento de objetos

Un servicio de objetos guarda objetos en buckets:

Servidor de objetos (S3 / MinIO)bucket miappdocumentos/2026/libro.pdfportadas/978316000123.webpAplicación (SDK)NavegadorAPI HTTPaccess key + secret keyURL directa o prefirmada

La aplicación habla con la API; el navegador solo con URLs: directas si el bucket es público, o prefirmadas (sección 3).

Sistema de ficheros Almacenamiento de objetos
Unidad Fichero en un árbol de carpetas Objeto con clave plana dentro de un bucket
Acceso Rutas locales del sistema API HTTP por red
Metadatos Los del sistema operativo Los que lleve cada objeto
Edición Los ficheros se modifican Los objetos se sobrescriben
Escalado Un servidor, su disco Distribuido en muchos nodos (pensado para nube)

2. S3: el estándar

S3 (*Simple Storage Service*) es el servicio de objetos de AWS. Su API es el estándar de facto: la implementan las demás nubes y servidores compatibles como MinIO, y el mismo código del SDK funciona contra cualquiera de ellos cambiando la configuración (endpoint y credenciales), nunca el programa.

Las operaciones básicas son pocas:

Operación Qué hace Como
CreateBucket crea el bucket mkdir
PutObject sube datos y metadatos bajo una clave cp
GetObject descarga los datos de una clave cat
HeadObject solo los metadatos (tipo, tamaño, fecha) stat
DeleteObject borra el objeto rm
ListObjectsV2 lista las claves del bucket, filtradas por prefijo ls

La autenticación usa credenciales access key / secret key: el SDK firma cada petición con ellas. No hay sesión ni token: la firma viaja en la petición y las credenciales solo las conoce la aplicación, que las recibe como configuración, nunca en el código.

3. Público, privado y URLs prefirmadas

Cada bucket define una política de acceso para sus objetos:

Una URL prefirmada (*presigned URL*) la genera la aplicación con sus credenciales: lleva incrustadas la firma y una expiración. Quien la recibe descarga el objeto sin credenciales durante ese tiempo; al caducar, la URL vuelve a dar 403. Es la forma de compartir un objeto privado sin exponer credenciales ni hacer copias.

4. ¿Un bucket o varios?

La tentación inicial es un bucket por sección de la aplicación (books, movies…). Lo idiomático es lo contrario: la organización por tipo de contenido se resuelve con prefijos, y los buckets se reservan para configuraciones distintas.

La configuración es del bucket y afecta a todo lo que contiene. Datos con la misma configuración: un bucket, separados por prefijos (documentos/, portadas/). Datos con una configuración diferente, como ficheros privados de la aplicación frente a imágenes públicas: otro bucket.

5. MinIO: S3 en local

MinIO es un servidor open source compatible con la API S3: un binario único que arranca en un segundo con Docker. En desarrollo simula AWS sin coste ni red; el código del SDK escrito contra MinIO funciona en producción contra S3 cambiando el endpoint y las credenciales, que viajan en configuración.

6. Arranque con Docker

$ docker run -d --name minio \
    -p 9000:9000 -p 9001:9001 \
    -e MINIO_ROOT_USER=admin \
    -e MINIO_ROOT_PASSWORD=secreto123 \
    -v minio-data:/data \
    elestio/minio server /data --console-address ":9001"

7. La consola y mc

La consola web permite crear buckets, subir y descargar ficheros, fijar la política de acceso de cada bucket y crear access keys para las aplicaciones (el usuario root del arranque no se usa en el código).

Para línea de comandos, mc (MinIO Client):

$ mc alias set local http://localhost:9000 admin secreto123   # conectar
$ mc mb local/miapp                                           # crear el bucket
$ mc cp libro.pdf local/miapp/documentos/libro.pdf            # subir
$ mc ls local/miapp                                           # listar

Con dos comandos más se practica el acceso público y prefirmado de la sección 3:

$ mc anonymous set download local/miapp
# abre http://localhost:9000/miapp/documentos/libro.pdf en el navegador

$ mc share download --expire 5m local/miapp/documentos/libro.pdf
# URL prefirmada: funciona 5 minutos y luego da 403

8. Uso desde Java

El SDK de AWS apunta a MinIO con dos ajustes: el endpoint local y el acceso por ruta (path-style), que es como funciona MinIO:

S3Client s3 = S3Client.builder()
        .endpointOverride(URI.create("http://localhost:9000"))
        .region(Region.US_EAST_1)
        .credentialsProvider(StaticCredentialsProvider.create(
                AwsBasicCredentials.create("admin", "secreto123")))
        .forcePathStyle(true)       // URL http://host:9000/bucket/clave
        .build();

s3.putObject(PutObjectRequest.builder()
                .bucket("miapp")
                .key("documentos/libro.pdf")
                .build(),
        RequestBody.fromFile(Path.of("libro.pdf")));

El mismo SDK genera URLs prefirmadas desde el código: una vez creado un S3Presigner con la misma configuración que el S3Client, la petición presignada devuelve la URL con expiración:

URL url = presigner.presignGetObject(GetObjectPresignRequest.builder()
        .signatureDuration(Duration.ofMinutes(10))
        .getObjectRequest(GetObjectRequest.builder()
                .bucket("miapp").key("documentos/libro.pdf").build())
        .build())
        .url();

La única diferencia entre el MinIO de desarrollo y S3 en producción es la configuración: URL del endpoint y credenciales. Por eso esos valores van en configuración (variables de entorno), nunca en el código: al cambiar de entorno cambia el endpoint, no el programa.

En el siguiente tema, el almacén de objetos entra en la arquitectura de la aplicación como un servicio más del compose.yaml.