Object Storage

Store app files in S3 Object Storage: Laravel, Node.js, Python, and your own domain

Connect a Laravel, Node.js, or Python app to AwanCerah Object Storage: file uploads, temporary download links, public access, and your own domain with automatic HTTPS.

Updated 12 November 2026 · 4 min read

Storing uploads in Object Storage keeps your app server light. Files survive a server rebuild and can be shared by several servers at once. This guide shows how to connect Laravel, Node.js, and Python to AwanCerah Object Storage, then serve public files from your own domain.

What you need

  1. An S3 Account and bucket. If you do not have one yet, follow Getting started with S3 Object Storage.
  2. An access key for the app: click Create access key, choose Specific buckets, and grant read and write access to the app's bucket.
  3. The endpoint and region from the S3 access card: https://s3.awanbaru.my.id and pwt1.

The examples below use a bucket named shop-images. Replace it with yours. We tested the code against the AwanCerah endpoint with the latest SDKs: the AWS SDK for PHP (used by Laravel's S3 driver), the AWS SDK for JavaScript v3, and boto3.

Laravel

Install Laravel's S3 driver:

composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies

Fill in .env. Laravel's default s3 disk in config/filesystems.php already reads these variables:

FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=<access key>
AWS_SECRET_ACCESS_KEY=<secret key>
AWS_DEFAULT_REGION=pwt1
AWS_BUCKET=shop-images
AWS_ENDPOINT=https://s3.awanbaru.my.id
AWS_USE_PATH_STYLE_ENDPOINT=true

Store files, create links, and delete files:

use Illuminate\Support\Facades\Storage;

// store a file from an upload form, returns a path like "products/abc123.jpg"
$path = $request->file('photo')->store('products', 's3');

// 15-minute download link for a private file
$url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(15));

// delete the file
Storage::disk('s3')->delete($path);

If the bucket is public, also set AWS_URL to the bucket's public address, for example https://shop-images.web.awanbaru.my.id. Then Storage::disk('s3')->url($path) returns a public link.

Node.js

Install the AWS SDK for JavaScript v3:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
  region: 'pwt1',
  endpoint: 'https://s3.awanbaru.my.id',
  forcePathStyle: true,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey: process.env.S3_SECRET_KEY,
  },
  // keeps temporary upload links (presigned PUT) working, see the note below
  requestChecksumCalculation: 'WHEN_REQUIRED',
});

// upload
await s3.send(new PutObjectCommand({
  Bucket: 'shop-images',
  Key: 'products/blue-shirt.jpg',
  Body: fileBuffer,
  ContentType: 'image/jpeg',
}));

// 15-minute download link
const url = await getSignedUrl(
  s3,
  new GetObjectCommand({ Bucket: 'shop-images', Key: 'products/blue-shirt.jpg' }),
  { expiresIn: 900 },
);

**Why requestChecksumCalculation?** Since early 2025, the AWS SDK for JavaScript v3 adds a checksum of the file contents to temporary upload links. The contents do not exist yet when the link is created, so uploads through that link fail with an InvalidDigest error. WHEN_REQUIRED restores the old behaviour. Regular uploads with PutObjectCommand are not affected.

Python

pip install boto3
import boto3

s3 = boto3.client(
    's3',
    endpoint_url='https://s3.awanbaru.my.id',
    region_name='pwt1',
    aws_access_key_id='<access key>',
    aws_secret_access_key='<secret key>',
)

# upload a file (large files are sent in parts automatically)
s3.upload_file('report.pdf', 'shop-images', 'documents/report.pdf')

# 15-minute download link
url = s3.generate_presigned_url(
    'get_object',
    Params={'Bucket': 'shop-images', 'Key': 'documents/report.pdf'},
    ExpiresIn=900,
)

For Django, the django-storages package uses the same settings: AWS_S3_ENDPOINT_URL, AWS_S3_REGION_NAME, and AWS_STORAGE_BUCKET_NAME.

Upload straight from the browser

Large files do not have to pass through your server: the browser can send them straight to S3 with a temporary upload link (presigned PUT). First allow your website in the bucket's CORS rules:

  1. Open the bucket, go to the Settings tab, and find the CORS card.
  2. Click Add rule, enter your website as the Origin, for example https://yourshop.com, and tick PUT for uploads and GET if the browser also downloads files.
  3. Click Save.

Your backend creates the temporary upload link as in the Node.js example above, and the browser sends the file to that link:

// in the browser: uploadUrl comes from your backend
await fetch(uploadUrl, {
  method: 'PUT',
  body: file,
  headers: { 'Content-Type': file.type },
});

If the link was created with a ContentType, the browser must send the same Content-Type header. Access keys stay on your backend and are never sent to the browser.

Temporary download links work directly in the browser without CORS rules, for example for download buttons or <img> tags.

Public files and your own domain

For product images, site assets, or files anyone may see, turn on public access for the bucket:

  1. Open the bucket and go to the Public access & domains tab.
  2. Turn on Public access. Every file in this bucket can then be opened without an access key, so keep private files in another bucket.
  3. Files are available at the bucket's public address, for example https://shop-images.web.awanbaru.my.id/products/blue-shirt.jpg.

To use your own domain, for example cdn.yourshop.com:

  1. In the Own domain card, enter the domain and click Add domain.
  2. The console shows the DNS record to create: a CNAME for a subdomain such as cdn.yourshop.com, or an A record for a root domain. Copy the type, name, and value exactly to your DNS provider.
  3. If the domain uses Cloudflare, turn off the proxy (DNS only). Also remove any AAAA record for that name.
  4. The status changes to Active once DNS is detected. Click Check now to check sooner.

The HTTPS certificate is issued automatically the first time the domain is opened.

Static sites

A public bucket can also serve a static site, such as a Hugo, Astro, or React build. Upload index.html to the root of the bucket along with the other files, and the bucket address shows index.html. A custom 404 page cannot be set yet, so React or Vue apps should use hash routing.

Tips

  • Keep public and private files in separate buckets.
  • Create one access key per app. If one key leaks, other apps are not affected.
  • Keep access keys in environment variables or a .env file, never in code that goes into Git.
  • When the S3 Account quota is full, uploads fail with an AccessDenied (403) error. Handle this error in your app, and upgrade the plan from the console when needed.

Move your app files to S3

Keep servers light while files are stored on two physical servers. Plans start at Rp1.000 a month.

Create an S3 Account

Still have a question?

Our team can help through live chat, email or WhatsApp.

Chat on WhatsApp support@awancerah.cloud