AWS CLI, SDK 이용해서 Cloudflare R2 조작하기

Cover Image for AWS CLI, SDK 이용해서 Cloudflare R2 조작하기
swhan
· 4 min read

AWS CLI, SDK를 이용하여 S3 API와 호환되는 Cloudflare R2를 다루는 방법을 알아봅시다.

준비 작업

클라우드플레어 쪽에서 S3 API와의 호환성 고려해서 R2 API를 설계해 놓았는데요, 저희같이 가볍게 사용할 사람들은 endpoint, region만 기억하시면 큰 학습비용 없이 대부분 상황에서 문제 없이 사용하실 수 있습니다.

버킷 레벨, 오브젝트 레벨 관련해서 더 정교한 호환성 점검이 필요하신 분들은 S3 API compatibility 참조하시면 됩니다.

AWS CLI 설치

저는 맥을 이용하기 때문에 Homebrew를 이용해서 aws-cli를 설치하도록 하겠습니다.

brew install awscli

Access key 발급

S3 API의 경우에는 액세스 키(Access key)를 사용해서 요청을 인증해야 하는데요, 액세스 키는 액세스 키 ID(access key ID), 비밀 액세스 키(secret access key) 두 가지 부분으로 구성되어 있습니다.

R2 API 요청에 사용할 키 발급을 위해서는 클라우드플레어 Dashboard 접속하시고 API 토큰 관리 선택

r2 dashboard

USER API 토큰 생성으로 이동하셔서

r2 사용자 api token 생성

보안을 위해서 아래처럼 권한을 넣어줍니다.

  • 권한: 객체 읽기 및 쓰기
  • 버킷 범위: 특정 버킷 지정

r2 사용자 api token 권한 설정

이제 액세스 키 ID, 비밀 액세스 키, 엔드포인트 정보를 알려주는데요, 발급된 키들은 보안을 위해 다시 표시되지 않으니 따로 복사해서 안전한 곳에 보관해주셔야 합니다.

r2 access key id, secret access key

사용하기

aws-cli 사용할 때 기존 aws 계정과 충돌나지 않도록 cloudflare라는 새로운 프로필 추가하고, 요청을 수신할 엔드포인트도 전역 변수로 등록해서 사용하도록 하겠습니다.

aws는 s3의 엔드포인트를 https://<bucket-name>.s3.<region-code>.amazonaws.com/<key-name> 이런 구조로 설계해 놓았는데요, 클라우드플레어의 엔드포인트는 리전 단위가 아니라 ACCOUNT_ID 단위로 설계된 것을 보실 수 있습니다.

export ENDPOINT_URL='https://<ACCOUNT_ID>.r2.cloudflarestorage.com'
cat ~/.aws/config

[profile cloudflare]
region = auto
output = json
cat ~/.aws/credentials

[cloudflare]
aws_access_key_id = 654b.....d56d
aws_secret_access_key = 1e1d9.....d4ab7

aws-cli는 사용자 편의를 위해서 상위 수준, 하위 수준 명령어들을 따로 제공해주는데요, Cloudflare R2 사용법 - 나만의 이미지 호스팅 서버 만들기 포스팅에서 만들어 놓은 r2-test 버킷을 이용해서 가장 많이 사용되는 명령어들만 빠르게 알아보겠습니다.

상위 수준(s3) 명령

aws s3 ls s3://r2-test \
--endpoint-url $ENDPOINT_URL \
--human-readable \
--profile cloudflare

2025-08-02 20:27:11   19.2 KiB hello-r2.webp
aws s3 cp ./README.md s3://r2-test \
--endpoint-url $ENDPOINT_URL \
--profile cloudflare

upload: ./README.md to s3://r2-test/README.md
aws s3 presign s3://r2-test/hello-r2.webp \
--endpoint-url $ENDPOINT_URL \
--expires-in 300 \
--profile cloudflare

https://<ACCOUNT_ID>.r2.cloudflarestorage.com/r2-test/hello-r2.webp?
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=<ACCESS_KEY_ID>%2F20250809%2Fauto%2Fs3%2Faws4_request&
X-Amz-Date=20250809T124128Z&
X-Amz-Expires=300&
X-Amz-SignedHeaders=host&
X-Amz-Signature=7f0dc....76c32

API 수준(s3api) 명령

aws s3api list-objects-v2 \
--bucket r2-test \
--endpoint-url $ENDPOINT_URL \
--profile cloudflare

{
  "Contents": [
    {
      "Key": "hello-r2.webp",
      "LastModified": "2025-08-02T11:27:11.306000+00:00",
      "ETag": "\"24351ce74aa07208fd51bdf70d0d76b9\"",
      "Size": 19632,
      "StorageClass": "STANDARD"
    }
  ],
  "RequestCharged": null,
  "Prefix": null
}

Node.js SDK

import { S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";

const client = new S3Client({
  region: "auto",
  endpoint: "https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
  credentials: {
    accessKeyId: "654b.....d56d",
    secretAccessKey: "1e1d9.....d4ab7",
  },
});

(async () => {
  const command = new HeadObjectCommand({
    Bucket: "r2-test",
    Key: "hello-r2.webp",
  });
  const output = await client.send(command);
})();

// {
//   '$metadata': {
//     httpStatusCode: 200,
//     attempts: 1,
//     totalRetryDelay: 0
//   },
//   AcceptRanges: 'bytes',
//   LastModified: 2025-08-02T11:27:11.000Z,
//   ContentLength: 19632,
//   ETag: '"24351ce74aa07208fd51bdf70d0d76b9"',
//   ContentType: 'image/webp',
//   Metadata: {}
// }