Sube archivos a MinIO con cURL y URL PUT prefirmadas
Para exportar un archivo local a MinIO, genera una URL PUT prefirmada para un bucket y una clave de
objeto; luego envía el archivo con curl --upload-file. En este tutorial compilarás un
servidor local, crearás el bucket y descargarás el objeto subido para comprobar que sus bytes
coinciden con los de tu archivo.
Comprende qué autoriza la URL
MinIO expone una API HTTP compatible con S3. Un SDK firma una solicitud con tus claves de acceso y
secreta; cURL envía los bytes usando esa firma. El SDK y el cliente de línea de comandos
mc son herramientas, no métodos de autenticación distintos.
Una URL prefirmada es una credencial al portador. Cualquiera que tenga la URL de este ejemplo puede hacer un PUT a su clave de objeto exacta durante 10 minutos, incluso reemplazarlo por otros bytes. No autentica el contenido de un archivo local específico. Mantenla fuera del historial del shell, los registros y los mensajes compartidos. Una subida mediante un formulario con una política POST es una operación distinta; usa la operación PUT prefirmada del SDK.
Prepara una demo local
El repositorio de la comunidad de MinIO se archivó el 25 de abril de 2026 y declara que ya no recibe mantenimiento. La compilación de la versión fija del código fuente que se muestra a continuación sirve para pruebas de compatibilidad locales, no es una recomendación para un nuevo despliegue en producción. Usa un servicio con mantenimiento para producción y sigue sus requisitos de autenticación y TLS.
Requisitos previos
Usa Linux con Bash, cURL, Go 1.26.8, Node.js 24.15.0 y Corepack con Yarn 4.12.0. Los ejemplos usan el
soporte integrado de TypeScript en Node;
los archivos .mts se ejecutan como módulos ES incluso dentro de un proyecto
padre CommonJS. La versión fija del servidor es RELEASE.2025-10-15T17-29-55Z, y la del SDK de
JavaScript es minio@8.0.7. Compilar el servidor requiere acceso a la red, espacio
en disco para las dependencias de Go y unos minutos de compilación.
Empieza en un directorio con permisos de escritura. El siguiente bloque crea un nuevo directorio
minio-curl-demo e instala su propio SDK. Rechaza un directorio existente. Su archivo
de bloqueo lo convierte en un proyecto de Yarn independiente. Su
configuración de paquetes y caché mantiene la instalación dentro de ese directorio. El shell padre
permanece en su directorio original.
(
mkdir minio-curl-demo &&
cd minio-curl-demo &&
printf '%s\n' '{"name":"minio-curl-demo","private":true,"packageManager":"yarn@4.12.0","dependencies":{"minio":"8.0.7"}}' > package.json &&
touch yarn.lock &&
printf 'nodeLinker: node-modules\nenableGlobalCache: false\n' > .yarnrc.yml &&
env COREPACK_HOME="$PWD/.corepack" YARN_IGNORE_PATH=1 \
YARN_GLOBAL_FOLDER="$PWD/.yarn/global" corepack yarn install
)
Configuración inicial
Compila el código fuente de la versión fija del servidor
en el directorio bin de la demo. Ejecuta esto desde el mismo directorio
padre que el bloque anterior. Si la compilación falla, detente aquí; no inicies un binario que haya
quedado de una compilación anterior.
(
cd minio-curl-demo &&
mkdir bin data go-path go-cache tmp &&
env GOENV=off GOWORK=off GOTOOLCHAIN=local \
GOPATH="$PWD/go-path" GOCACHE="$PWD/go-cache" GOTMPDIR="$PWD/tmp" \
GOBIN="$PWD/bin" go install github.com/minio/minio@RELEASE.2025-10-15T17-29-55Z
)
Abre dos terminales Bash en ese directorio padre. Pega esta configuración en ambas terminales. Elige dos puertos sin usar si estos están ocupados y usa los mismos valores en ambas terminales. Las credenciales son valores de demostración deliberadamente públicos; úsalas solo con este servidor, accesible únicamente mediante la interfaz de loopback.
export MINIO_API_PORT=19000
export MINIO_CONSOLE_PORT=19001
export MINIO_ENDPOINT="http://127.0.0.1:${MINIO_API_PORT}"
export MINIO_ACCESS_KEY='curl-demo-admin'
export MINIO_SECRET_KEY='local-demo-only-password'
En la primera terminal, inicia el servidor en primer plano. Déjalo en ejecución mientras usas la
segunda terminal. Presiona Ctrl+C en la primera terminal para detenerlo al terminar; el shell
continúa y los objetos permanecen en minio-curl-demo/data.
(
cd minio-curl-demo &&
MINIO_ROOT_USER="$MINIO_ACCESS_KEY" MINIO_ROOT_PASSWORD="$MINIO_SECRET_KEY" \
./bin/minio server ./data --address "127.0.0.1:${MINIO_API_PORT}" \
--console-address "127.0.0.1:${MINIO_CONSOLE_PORT}"
)
Crea el bucket y el firmador
Guarda cada uno de los siguientes archivos TypeScript dentro de minio-curl-demo.
El cliente compartido comprueba que el endpoint sea un origen, sin prefijo de ruta ni credenciales
integradas. Su región coincide con la de este servidor local. Para un despliegue existente, usa su
región real y credenciales limitadas al ámbito del bucket; las credenciales raíz de este ejemplo
solo sirven para aprovisionar la demo privada.
Guarda el archivo como minio-client.mts:
import { Client } from 'minio'
export function createClient(): Client {
const { MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY } = process.env
if (!MINIO_ENDPOINT || !MINIO_ACCESS_KEY || !MINIO_SECRET_KEY) {
throw new Error('Set the endpoint and credentials')
}
const endpoint = new URL(MINIO_ENDPOINT)
if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.username ||
endpoint.password || endpoint.pathname !== '/' || endpoint.search || endpoint.hash) {
throw new Error('Use an HTTP or HTTPS origin without credentials or a path prefix')
}
return new Client({
endPoint: endpoint.hostname,
port: Number(endpoint.port || (endpoint.protocol === 'https:' ? 443 : 80)),
useSSL: endpoint.protocol === 'https:',
accessKey: MINIO_ACCESS_KEY,
secretKey: MINIO_SECRET_KEY,
region: 'us-east-1',
})
}
Guarda el archivo como prepare-bucket.mts. Firmar una URL no crea un bucket; este paso sí.
import { createClient } from './minio-client.mts'
async function main(): Promise<void> {
const [bucket, ...extra] = process.argv.slice(2)
if (!bucket || extra.length) throw new Error('Usage: node prepare-bucket.mts BUCKET')
const client = createClient()
if (!(await client.bucketExists(bucket))) await client.makeBucket(bucket, 'us-east-1')
console.log(`Bucket ready: ${bucket}`)
}
main().catch(() => {
console.error('Bucket setup failed; check the server, credentials, and bucket name')
process.exitCode = 1
})
En la segunda terminal, crea el bucket. Una ejecución correcta imprime Bucket ready: curl-demo.
Si el servidor no está listo, espera su mensaje de inicio y vuelve a ejecutar este comando.
(cd minio-curl-demo && node prepare-bucket.mts curl-demo)
Genera URL prefirmadas
Guarda el archivo como presign.mts. Firma operaciones PUT o GET y emite una línea
de configuración de cURL, que los comandos de subida y verificación pasan por la entrada estándar.
Ambas operaciones usan un plazo de caducidad de 600 segundos. La
referencia de la API del SDK
documenta estos dos métodos distintos.
import { createClient } from './minio-client.mts'
async function main(): Promise<void> {
const [method, bucket, key, ...extra] = process.argv.slice(2)
if (!bucket || !key || extra.length || !['put', 'get'].includes(method ?? '')) {
throw new Error('Usage: node presign.mts put|get BUCKET KEY')
}
const client = createClient()
const url = method === 'put'
? await client.presignedPutObject(bucket, key, 600)
: await client.presignedGetObject(bucket, key, 600)
console.log(`url = ${JSON.stringify(url)}`)
}
main().catch(() => {
console.error('Signing failed; check the method, bucket, key, endpoint, and credentials')
process.exitCode = 1
})
No ejecutes el firmador por separado en una terminal cuya sesión se esté grabando: su salida contiene la URL prefirmada. El SDK codifica la clave del objeto. Pasa la clave original al firmador y deja intacta la URL resultante, incluidos su host, puerto, ruta y cadena de consulta.
Sube un archivo y registra el resultado
Guarda el archivo como upload.sh dentro del directorio de la demo. Ejecútalo con
Bash; no lo cargues con source. Acepta un archivo local regular, un bucket y una clave de objeto
explícita. Los archivos vacíos son válidos. Ante nombres de archivo como
- o -report.bin, añade el prefijo
./ para que identifiquen archivos en lugar de la entrada estándar de cURL.
#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -ne 3 ] || [ ! -f "$1" ] || [ ! -r "$1" ] || [ "$1" = '-' ]; then
printf 'Usage: bash upload.sh FILE BUCKET KEY (readable regular file)\n' >&2
exit 1
fi
file=$1
bucket=$2
key=$3
configuration=$(node presign.mts put "$bucket" "$key") || exit 1
if printf '%s\n' "$configuration" |
curl -q --config - --globoff --path-as-is --fail --silent \
--connect-timeout 5 --max-time 120 --upload-file "$file" \
--output /dev/null --write-out 'HTTP %{http_code}\n' 2>/dev/null |
tee -a upload.log; then
printf 'Uploaded %s to %s/%s\n' "$file" "$bucket" "$key"
else
printf 'Upload or status logging failed for %s; verify the object before retrying\n' "$file" >&2
exit 1
fi
-q es la primera opción de cURL para que ignore el
.curlrc de quien lo invoca. --config - lee la URL desde la
entrada estándar y la mantiene fuera de los argumentos del proceso de cURL.
--globoff conserva los corchetes y las llaves de los nombres de archivo como
caracteres literales. --fail hace que un rechazo HTTP provoque un fallo de
la transferencia, y pipefail propaga los fallos de transferencia o de registro
a lo largo del pipeline. La asignación comprueba el firmador por separado. El script suprime los
diagnósticos de transferencia sin procesar y registra solo el estado HTTP, no el cuerpo de la
respuesta ni la URL. Consulta el
manual de cURL para conocer la sintaxis de configuración y las opciones.
Ejecuta esto desde el directorio padre en la segunda terminal. Crea o reemplaza el archivo local
de la demo sample.txt y lo sube con una clave que contiene espacios y un signo más:
(
cd minio-curl-demo &&
printf 'hello, MinIO\n' > sample.txt &&
bash upload.sh sample.txt curl-demo 'exports/sample + 1.txt'
)
La salida esperada es HTTP 200 seguida de Uploaded sample.txt to curl-demo/exports/sample + 1.txt.
El bucket no tiene control de versiones: un PUT correcto a una clave existente reemplaza sus
bytes anteriores. Elige una clave nueva si quieres conservar el objeto anterior. Una URL
caducada, una firma incorrecta o un bucket inexistente deben provocar un fallo sin el mensaje
Uploaded.
Descarga y compara los bytes almacenados
Un HTTP 200 demuestra que el servidor aceptó el PUT. Comprueba el objeto almacenado por separado
con un GET firmado. Este bloque escribe downloaded.txt, reemplazando ese archivo
local si la descarga se lleva a cabo, y luego lo compara con sample.txt.
Si la comparación confirma que coinciden, imprime Verified identical bytes.
(
set -euo pipefail
cd minio-curl-demo || exit 1
configuration=$(node presign.mts get curl-demo 'exports/sample + 1.txt') || exit 1
if printf '%s\n' "$configuration" |
curl -q --config - --globoff --path-as-is --fail --silent \
--connect-timeout 5 --max-time 120 --output downloaded.txt 2>/dev/null; then
cmp sample.txt downloaded.txt && printf 'Verified identical bytes\n'
else
printf 'Download failed; check the object and signing configuration\n' >&2
exit 1
fi
)
Diagnostica una subida fallida
Si falla la firma, revisa la configuración del entorno y los tres argumentos del script. Se
rechaza cualquier endpoint con un prefijo de ruta de proxy, como https://storage.example.com/minio/.
Usa el origen de la API S3 del servicio y usa HTTPS para un endpoint remoto.
HTTP 000 significa que no se obtuvo ninguna respuesta HTTP. Comprueba que el
servidor esté en ejecución en el puerto configurado, que el archivo se pueda leer y que el
certificado sea de confianza. No desactives la validación de certificados para lograr que una
subida remota se complete. HTTP 403 puede indicar una firma no válida o
caducada, credenciales incorrectas, permisos denegados o un desfase de reloj.
HTTP 404 puede indicar un bucket inexistente; el firmador puede generar una
URL para un bucket que no existe.
Puede producirse un fallo de registro después de que el servidor haya almacenado el objeto.
Por ejemplo, upload.log podría ser un directorio o no permitir la escritura.
El script informa de un fallo porque no pudo registrar el estado; comprueba el objeto con GET
antes de decidir si vuelves a intentarlo. La desconexión de un cliente también puede dejar el
resultado incierto. Reutilizar la misma clave puede reemplazar un objeto que ya se había aceptado.
Elige el siguiente paso
Este script envía un archivo en un único PUT, con un límite de 120 segundos para la transferencia. No implementa subidas multiparte, transferencias reanudables ni sincronización de directorios. Para esas tareas, usa un SDK o un cliente de almacenamiento con el comportamiento necesario y revisa su política de reemplazo. Para un flujo de trabajo por lotes específico de AWS, consulta las exportaciones por lotes a Amazon S3 con cURL y la AWS CLI.
