Cloudflare R2 is a cloud-based data storage solution designed to help developers and businesses securely store and manage their data through s3 compatible object storage. What this means is that by using the official AWS S3 client, you are able to change providers by changing your credentials.
What are the benefits of using Cloudflare R2?
When building a website or product, you'll often need to serve static content and assets such as images, fonts, videos and stylesheets. Using an object storage solution allows you to transfer the hosting of these files to a provider who then is responsible for the security, storage and distribution via a content delivery network (CDN). As Cloudflare makes use of over 200 datacenters in over 100 countries, your content will always be located close to your users making for faster loading times than if you were serving this content from your own server.
What is the consistency model of R2?
Cloudflare R2 is a strongly consistent storage system, meaning that when a create, get or delete request is made, it will affect all POPs (Points of Presence).
The only action which is eventually consistent is adding and removing storage permissions for API keys which can take up to a minute to reflect across the network.
How much will it cost me?
Cloudflare being part of the "Bandwidth Aliance" offers zero egress fees. This means that you do not pay for outgoing bandwidth from R2, however you do pay for request types. More on pricing can be found here, however in as of early 2023:
The Free tier offers (per month) 10GB of storage, 1 million Class A (mutations) requests and 10 million Class B (read) requests.
Once you exceed this, the Paid Tier offers storage at $0.015/gb, 1 million Class A (mutation) requests for $4.50 and 1 million Class B (read) requests for $0.36.
How do I use CloudFlare R2 with Go?
While Cloudflare does offer its own set of libraries found here, the sweet spot is using the S3 compatible client which allows you to avoid vendor lock-in. That's what we'll be using today.
You'll first need to create a bucket and make note of your account ID. You'll then need to click the "Manage R2 API Tokens"

Here, you'll want to set the the permissions as Permissions - Edit. Once created, take note of your Access Key ID and Secret Key ID as they won't be shown again.
Note: It's good security practice to not store these credentials in your code and commit them to git. Instead load them in via environment variables.
First we'll want to create a simple main.go file which can hold the *s3.client which we can then re-use around this service. While we're at it, we'll also load in a file from memory called image.png
.
type App struct {
R2 *s3.Client
}
func main() {
ctx := context.TODO()
// a := App{}
_, err := os.ReadFile("image.png")
if err != nil {
log.Fatal(err)
}
}
Now we'll create our NewR2()
func which will instantiate S3 with our credentials and return us an *s3.Client
.
func NewR2(ctx context.Context) *s3.Client {
accountId := ""
accessKeyId := ""
accessKeySecret := ""
r2Resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: fmt.Sprintf("https://%s.r2.cloudflarestorage.com", accountId),
}, nil
})
cfg, err := config.LoadDefaultConfig(ctx,
config.WithEndpointResolverWithOptions(r2Resolver),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKeyId, accessKeySecret, "")),
)
if err != nil {
log.Fatal(err)
}
return s3.NewFromConfig(cfg)
}
After this, we'll want to create a function which makes use of the s3 client and uploads files for us without needing to write the putObject code every time. The below code will attempt to upload the file body
with the given filename
and if anything breaks it'll return an error. No error tells us that the file is uploaded!
func (a *App) uploadFile(ctx context.Context, filename string, body io.Reader) error {
_, err := a.R2.PutObject(ctx, &s3.PutObjectInput{
Body: body,
Key: aws.String(filename),
Bucket: aws.String("workingolang"), // replace with your bucket name!
})
if err != nil {
return err
}
return nil
}
Finally bringing it all together, we have the following code which will upload the file every time you run this simple go program.
package main
import (
"bytes"
"context"
"fmt"
"io"
"log"
"os"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
type App struct {
R2 *s3.Client
}
func main() {
ctx := context.TODO()
a := App{
R2: newR2(ctx),
}
content, err := os.ReadFile("image.png")
if err != nil {
log.Fatal(err)
}
err = a.uploadFile(ctx, "myImage.png", bytes.NewReader(content))
if err != nil {
log.Fatal(err)
}
}
func newR2(ctx context.Context) *s3.Client {
accountId := ""
accessKeyId := ""
accessKeySecret := ""
r2Resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: fmt.Sprintf("https://%s.r2.cloudflarestorage.com", accountId),
}, nil
})
cfg, err := config.LoadDefaultConfig(ctx,
config.WithEndpointResolverWithOptions(r2Resolver),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKeyId, accessKeySecret, "")),
)
if err != nil {
log.Fatal(err)
}
return s3.NewFromConfig(cfg)
}
func (a *App) uploadFile(ctx context.Context, filename string, body io.Reader) error {
_, err := a.R2.PutObject(ctx, &s3.PutObjectInput{
Body: body,
Key: aws.String(filename),
Bucket: aws.String("workingolang"), // replace with your bucket name!
})
if err != nil {
return err
}
return nil
}
At this point, if everything works as expected, you should be able to refresh the Cloudflare R2 dashboard and see your new asset has been uploaded.
To view the file, you should set up a domain linked to the bucket, which will then make files within this bucket accessible at the desired url/<filename>
.
Summary
In summary, Setting up Cloudflare R2 with Go is not as hard as it seems, and it makes offloading images and assets to a global CDN very easy thanks to its S3 compatibility. The free tier is generous for small projects, and even the costs associated beyond that are resonable making work in golang enjoyable as always. Happy developing!