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

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 토큰 관리
선택
USER API 토큰 생성
으로 이동하셔서
보안을 위해서 아래처럼 권한을 넣어줍니다.
- 권한: 객체 읽기 및 쓰기
- 버킷 범위: 특정 버킷 지정
이제 액세스 키 ID
, 비밀 액세스 키
, 엔드포인트
정보를 알려주는데요, 발급된 키들은 보안을 위해 다시 표시되지 않으니 따로 복사해서 안전한 곳에 보관해주셔야 합니다.
사용하기
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: {}
// }