Skip to content

Storage

VentureKit provides storage through @venturekit/storage with S3 bucket configuration, CloudFront CDN, and lifecycle policies.

Terminal window
npm install @venturekit/storage@dev

Add storage definitions on defineVenture() in vk.config.ts:

export default defineVenture({
base,
security,
storage: [
{ id: 'uploads', purpose: 'uploads', versioned: true },
{ id: 'assets', purpose: 'assets' },
{ id: 'backups', purpose: 'backups' },
],
envs: { dev, prod },
});

Override per environment (e.g. config/prod.ts):

export const prod: EnvConfigInput = {
preset: 'medium',
storage: [
{ id: 'assets', cdn: true, corsOrigins: ['https://app.example.com'] },
],
};

VentureKit auto-prefixes bucket names with {project}-{stage} at deploy time (e.g. myapp-uploads-dev).

PurposeDescriptionTypical Config
uploadsUser-uploaded filesVersioned, private access
assetsStatic assets (images, CSS, JS)CDN-enabled, public read
backupsDatabase and application backupsPrivate, lifecycle rules
logsApplication and access logsPrivate, expiration rules

After deployment, storage outputs are available via ctx.intentOutputs:

import { handler } from '@venturekit/runtime';
import type { IntentOutputs } from '@venturekit/core';
export const main = handler(async (_body, ctx, logger) => {
const outputs = ctx.intentOutputs as IntentOutputs;
const uploadsBucket = outputs.storage['uploads'].bucketName;
const assetsCdnUrl = outputs.storage['assets'].cdnUrl;
logger.info('Storage info', { uploadsBucket, assetsCdnUrl });
return { uploadsBucket, assetsCdnUrl };
});

Enable CloudFront CDN for fast global delivery:

{
id: 'assets',
purpose: 'assets',
cdn: true,
cdnDomain: 'cdn.example.com', // Custom domain (optional)
corsOrigins: ['*'],
}

The CDN URL is available in outputs.storage['assets'].cdnUrl.

The @venturekit/storage package includes a full-featured runtime client for interacting with S3 (production) and MinIO (local dev). It auto-detects the environment.

import { createStorageClient, createStorageClientFromEnv } from '@venturekit/storage';
// From explicit config
const storage = createStorageClient({ bucket: 'my-uploads' });
// From environment variables (recommended)
const storage = createStorageClientFromEnv();
// Simple upload
await storage.put('avatars/user-123.jpg', imageBuffer, {
contentType: 'image/jpeg',
cacheControl: 'max-age=31536000',
});
// Multipart upload for large files
await storage.upload('videos/intro.mp4', videoStream, {
contentType: 'video/mp4',
onProgress: ({ loaded, total }) => console.log(`${loaded}/${total}`),
});
// Download to buffer
const { body, contentType } = await storage.get('avatars/user-123.jpg');
// Download as stream (for large files)
const { body: stream } = await storage.getStream('videos/intro.mp4');
stream.pipe(res); // pipe to HTTP response
// Signed download URL (client can download directly from S3)
const downloadUrl = await storage.getSignedUrl('avatars/user-123.jpg', {
expiresIn: 3600, // 1 hour
});
// Signed upload URL (client can upload directly to S3)
const uploadUrl = await storage.getSignedUploadUrl('avatars/user-456.jpg', {
contentType: 'image/jpeg',
expiresIn: 600, // 10 minutes
});
// List objects
const { objects, prefixes } = await storage.list({
prefix: 'avatars/',
delimiter: '/',
});
// List all (auto-paginate)
const allFiles = await storage.listAll({ prefix: 'uploads/' });
// Check existence
const exists = await storage.exists('avatars/user-123.jpg');
// Copy
await storage.copy('avatars/old.jpg', 'avatars/new.jpg');
// Move
await storage.move('temp/upload.jpg', 'avatars/final.jpg');
// Delete
await storage.delete('avatars/old.jpg');
// Bulk delete
await storage.deleteMany(['temp/a.jpg', 'temp/b.jpg']);

Use the prefix option to isolate storage per tenant:

const tenantStorage = createStorageClient({
bucket: 'my-uploads',
prefix: `tenants/${ctx.tenant.id}/`,
});
// All operations are scoped to the tenant
await tenantStorage.put('avatar.jpg', buffer);
// Actually stored at: tenants/{tenantId}/avatar.jpg

During vk dev, the storage client automatically connects to MinIO (started via shared Docker Compose at ~/.vk/). No configuration changes needed. All projects share the same MinIO instance on standard ports.

The following env vars are set automatically:

VariableExample
VENTURE_LOCALtrue
VENTURE_MINIO_ENDPOINThttp://localhost:9000
VENTURE_STORAGE_BUCKETmyapp-uploads

Buckets are project-prefixed and auto-created on first use. Open http://localhost:9001 for the MinIO console (credentials: minioadmin / minioadmin).