Montar mi propio alojamiento de imágenes de bajo coste con Function Compute de Aliyun + OSS

Tres cosas antes de empezar:
- Este alojamiento de imágenes es sencillo, fácil de configurar y suficientemente seguro; si se te queda corto, pásale el código fuente a una IA y podrás añadir o quitar funciones sin problema.
- Todos los
AccessKey, contraseñas, dominios y nombres de bucket de este artículo son marcadores de posición; reemplázalos por los tuyos antes de copiar nada. - El código es un único archivo Node.js (unas 380 líneas), las dependencias se inyectan mediante una capa (Layer) de FC, el código de negocio no tiene ningún valor hardcodeado y toda la configuración va por variables de entorno.
Código fuente: https://github.com/ezzty/oss-upload
1. Por qué montar tu propio alojamiento de imágenes
Casi todo blog estático necesita un alojamiento de imágenes. Muchos se ahorran dinero usando el “gratis para siempre” de otro; pero ¿qué gran benefactor nunca cierra? Además, los datos están en manos de otro y tú sin ningún margen de maniobra, así que uno acaba siempre con la mosca tras la oreja.
Después de tropezar con muchos problemas, decidí montarme el mío con un coste mínimo: ahorrar dinero, sin límite de capacidad y con garantías de seguridad.
Muchos planes de autoalojamiento exigen un servidor propio (decenas de yuanes al mes de alquiler y encima parchearlo a mano) o solo funcionan en un cliente local, de modo que si cambias de equipo ya no puedes subir nada; al final decidí hacer una solución serverless en la nube para subir cuando quieras y desde donde quieras.
Aliyun Function Compute FC + OSS + CDN
FC de pago por uso (con poco tráfico ≈ 0 yuanes), sin base de datos y sin mantenimiento, con paquetes de despliegue de solo 6 KB. Con subidas poco frecuentes el gasto es casi cero.
El almacenamiento de imágenes en OSS también es por uso: decenas de GB de imágenes al mes se quedan en unos pocos yuanes.
Y con CDN por delante, ¡las imágenes cargan a toda velocidad!
Aunque un alojamiento personal no tiene mucho valor como objetivo de ataque, igualmente he añadido varias capas de seguridad: PoW obligatorio contra fuerza bruta, validación de magic bytes contra archivos disfrazados, triple validación de tamaño contra inundaciones, cuota diaria contra abusos y token con 2 horas de vigencia contra usos indebidos.
Este esquema tampoco carece de defectos: sin multiusuario, sin gestión de álbumes, sin estadísticas de uso; para borrar una imagen ni siquiera hay interfaz propia, hay que entrar en la consola de OSS.
Pero es pequeño, cuesta poco y para uso personal sobra de sobra.
2. Arquitectura y flujo de peticiones
Navegador upload.html
│ ① POST /login {user, pass}
│ ← 200 {token} (válido 24 h)
│ ② POST /upload Authorization: Bearer *** + binario de la imagen
▼
Function Compute de Aliyun (Node.js 18, disparador HTTP)
│ Autenticación / limitación de tasa / validación de magic bytes / validación de tamaño
│ ③ ossClient.put(key, stream)
▼
Bucket de OSS (lectura y escritura privadas, lectura pública prohibida)
│ ④ Recuperación de origen del CDN (red interna, gratis)
▼
https://img.example.com/m/26j13/ab12cd.jpg ← dominio público de acceso
Tres decisiones de diseño:
- Bucket privado: no se concede a nadie permiso de lectura por enlace directo; las imágenes solo se pueden ver a través de la URL del dominio de CDN vinculado. Si lo dejas en lectura pública, tu bucket se convierte en un recurso común: cualquiera puede listarlo y consumir tu tráfico sin pagar.
- Hay que iniciar sesión para subir: si no, tu dominio de API se usará como alojamiento de imágenes gratuito y tanto el coste como el almacenamiento se te irán de las manos.
- El código de negocio nunca toca las claves: el AK/SK solo existen en las variables de entorno; en el código y en el repositorio nunca aparecen.
3. Trabajo previo
3.1 Activar los productos
Activa Function Compute FC y OSS (Object Storage Service) (ambos de pago por uso, sin prepaid).
3.2 Crear el bucket
- Nombre: debe ser único a nivel global, por ejemplo
my-img - Región: elige la más cercana a ti/tus usuarios, por ejemplo
China East 1 (Hangzhou)→oss-cn-hangzhou - Permiso de lectura/escritura: privado (importante)
- Tipo de almacenamiento: almacenamiento estándar (las imágenes de acceso frecuente deben usar esto; no elijas Infrequent Access ni Archive — esos tienen una duración mínima de almacenamiento y si los borras antes de tiempo te cobran de más)
3.3 Crear un usuario RAM con permisos limitados a un solo bucket (muy recomendable)
Muchos tutoriales te dicen directamente que uses la AccessKey de la cuenta principal — no lo hagas. Que se filtre el AK principal equivale a que se pierda toda tu cuenta de Aliyun.
Consola RAM → Políticas de permiso → Crear política de permiso → pestaña JSON:
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:GetObject",
"oss:PutObject",
"oss:DeleteObject",
"oss:ListObjects",
"oss:HeadObject"
],
"Resource": [
"acs:oss:*:*:my-img",
"acs:oss:*:*:my-img/*"
]
}
]
}
Nombre de la política: ImageHostSingleBucket. Después:
- RAM → Usuarios → Crear usuario, marcando “Acceso con AccessKey permanente”
- Vincular solo la política de arriba a este usuario
- Generar el AK/SK y guardarlo de inmediato en tu gestor de contraseñas local
Algunas decisiones deliberadas:
- No damos
oss:ListBuckets— si se filtra, ni siquiera se podrán ver qué buckets tienes - No damos
oss:CopyObject— el alojamiento de imágenes no necesita permisos de copia; damos lo mínimo posible - No vincules
AliyunOSSFullAccess— eso permitiría al AK de tu alojamiento leer todos tus buckets de datos
4. Preparar la capa de dependencias (Layer)
El código de negocio solo depende de ali-oss. Al convertirlo en una capa, el paquete de código se queda en pocos KB.
Opción A: descargar el adjunto del Release directamente (recomendado)
El proyecto ya publica un Release, y layer.zip y el paquete de código se pueden descargar tal cual:
https://github.com/ezzty/oss-upload/releases/latest
| Adjunto | Tamaño | Contenido |
|---|---|---|
layer.zip |
3,25 MB | ali-oss@6.23.0 y sus dependencias, 2338 archivos en total |
oss-upload-code.zip |
unos 6 KB | index.js + package.json, paquete de despliegue de la función |
Ventajas: no necesitas instalar Node.js ni empaquetar un zip, y evitas el problema de que tu npm install local obtenga una versión más nueva del SDK y genere una “diferencia con lo que el autor validó”. La capa contiene solo código fuente de paquetes npm, sin credenciales.
Opción B: construirlo tú mismo
Si quieres cambiar la versión de una dependencia o no confías en el paquete ya hecho, empaquétalo tú:
mkdir -p layer-build/nodejs
cd layer-build
npm init -y
npm install ali-oss
# El directorio debe tener esta estructura:
# nodejs/
# node_modules/
# ali-oss/
# ...sus dependencias transitivas
cd nodejs && zip -r ../layer.zip node_modules && cd ..
Subida
Consola de FC → Gestión de capas → Crear capa:
- Nombre:
oss-upload-deps - Runtime: Node.js 18
- Subir
layer.zip
La capa solo hay que crearla una vez. Para actualizar la versión de
ali-oss, empaqueta y publica una versión nueva y apunta la función a esa versión; no hace falta tocar el código de negocio.
5. Desplegar la función
5.1 Obtener el código de negocio
Recomendado: usar directamente el adjunto del Release: https://github.com/ezzty/oss-upload/releases/latest y descargar oss-upload-code.zip (unos 6 KB); al descomprimirlo ya tienes el paquete de despliegue estándar.
Si empaquetas el tuyo, en la raíz del zip solo pueden haber estos dos archivos (no lo anides en una carpeta, o el Handler no encontrará index.handler):
code.zip
├── index.js ← código fuente del negocio
└── package.json ← declara las dependencias (solo referencia; en runtime se cargan desde la capa)
zip code.zip index.js package.json
5.2 Crear la función
- Runtime: Node.js 18
- Handler:
index.handler - Especificación: 0,05 vCPU / 128 MB de memoria / 512 MB de disco (la mínima que funciona en la práctica)
- Configuración de capas: vincular la
oss-upload-depsque acabas de crear
Con el tema de la memoria hay que ir con cuidado: el código lee el body completo en memoria (
bodyBuf) y al decodificar base64 genera además una copia. Con 128 MB las imágenes normales van sobradas, pero si subes el límite de subida a 20 MB y sueles enviar imágenes grandes cercanas al límite, habrá que subir la memoria en consecuencia.
5.3 Variables de entorno (8 obligatorias, sin una sola no arranca)
La función tiene 8 variables de entorno obligatorias; configúralas según la imagen:
AUTH_USERadmin— usuario de inicio de sesiónAUTH_PASS<configura una contraseña fuerte>— contraseña de inicio de sesión; usa una cadena aleatoria largaJWT_SECRET<cadena aleatoria de más de 32 caracteres>— clave de firma del tokenOSS_ACCESS_KEY_IDLTAIxxxxxxxxxxxxxxxx— AK del usuario RAMOSS_ACCESS_KEY_SECRET<tu AccessKey Secret>— SK del usuario RAMOSS_BUCKETmy-img— nombre del bucketOSS_REGIONoss-cn-hangzhou— ID de la región del bucketCDN_DOMAINhttps://img.example.com— debe llevar el prefijo de protocolo, si no, el arranque falla
1 opcional:
ALLOWED_ORIGINSexample.com,myblog.net— orígenes permitidos para CORS, separados por comas. Sin definirlo también arranca; solo interviene cuando el CORS a nivel de código está activado (por defecto el CORS lo gestiona la plataforma devolviendo elOrigin, así queisValidOriginni se llega a llamar). Si activas el CORS a nivel de código sí que hace falta definirlo, porque si no el valor por defectoexample.combloquearía todas las subidas desde el navegador.

5.4 Vincular el dominio personalizado
Consola de FC → Gestión de dominios → Añadir dominio → sigue las instrucciones para configurar el CNAME y el certificado. Así obtienes una dirección de API como https://api.example.com.
6. Página frontend
upload.html es un frontend de un solo archivo; basta con soltarlo en el directorio de tu propio sitio.
Hay que cambiar una cosa: busca _FC_PARTS:
// Cámbialo por tu dominio personalizado de Function Compute
const _FC_PARTS = ['https://', 'api.example.com'];
El límite de tamaño debe coincidir con el del backend (por defecto ambos son 20 MB):
if (file.size > 20 * 1024 * 1024) { /* bloquear */ }
Hay un límite en el frontend y otro en el backend, y manda el más restrictivo. Si solo cambias el frontend, los archivos grandes recibirán un 413 en el servidor; si solo cambias el backend, el usuario quedará bloqueado en el navegador y ni siquiera podrá enviar nada.
7. ¿Quieres ir a fondo? El detalle está en el README del código fuente
Ese es el flujo completo para tener en marcha tu propio alojamiento de imágenes. El porqué del código, el detalle de cada capa de protección y la lista de verificación completa están recopilados en el README del repositorio; consúltalo cuando lo necesites:
https://github.com/ezzty/oss-upload → las secciones “🧩 Implementación clave (a nivel de código)” y “🧪 Verificación de puesta en producción” del README.md
En el cuerpo del blog solo he querido explicar el porqué de montarlo tú, cómo ponerlo en marcha y qué tener en cuenta; cuando de verdad vayas a desplegarlo, el README es la documentación técnica completa.
8. Coste
Pago por uso, en tres bloques:
| Concepto | Forma de facturación | Referencia de magnitud |
|---|---|---|
| Cálculo de FC | CU de cálculo (vCPU + memoria × duración) + número de invocaciones | Hay cuota gratuita; un alojamiento personal suele moverse cerca de ese límite |
| Almacenamiento en OSS | yuanes/GB/mes | Almacenamiento estándar unos 0,09~0,12 yuanes/GB/mes |
| Peticiones a OSS | Por petición | Muy bajo, se puede ignorar |
| Tráfico CDN | Yuanes/GB | Ver abajo |
El tráfico es la partida grande de la factura, y también la más fácil de optimizar:
- Subida a OSS: gratis
- Red interna de OSS (FC y OSS en la misma región): gratis
- Salida por la red pública de OSS: de pago (del orden de 0,5 yuanes/GB, varía según la región y el tramo horario)
- Si pasa por CDN: la recuperación de origen del CDN va por la red interna y ese tráfico es gratis; solo se paga el tráfico del CDN
Conclusión: el alojamiento de imágenes debe ir siempre detrás de un CDN, que además de acelerar te evita la tarifa de salida pública de OSS. Ojo también a que FC y OSS deben estar en la misma región: si están en regiones distintas el tráfico sale por la red pública, que es más lenta y puede generarte costes adicionales.
Según mis propias mediciones, si subes unas pocas decenas de imágenes al mes, el coste ronda el 1 yuan al mes.
9. Mantenimiento y actualizaciones
Cambiar el código de negocio (por ejemplo, el formato de los enlaces de imagen):
- Modifica
index.jsen local zip code.zip index.js package.json- Consola → Despliegue de código → Subir → Desplegar
- Ejecuta las sondas de verificación del README para confirmar que la nueva versión está activa
Cambiar el frontend:
- Modifica
upload.html - Súbelo al sitio; se recomienda
Cache-Control: no-cache, o los usuarios podrían recibir la versión antigua
Actualizar dependencias:
- Vuelve a ejecutar
npm install ali-ossy empaquetalayer.zip - Publica una nueva versión de la capa en la consola
- Cambia la función a la nueva versión de la capa
Rotar credenciales (hazlo periódicamente):
- Genera un AK/SK nuevo en RAM
- Actualiza las variables de entorno de FC → despliega
- Cuando confirmes que el servicio funciona, deshabilita el AK antiguo en la consola de RAM