Skip to content

Cloudflare R2 Image Backup

This repo uses Cloudflare R2 (S3-compatible object storage) as a free backup/CDN for images. Local markdown files keep using relative paths -- R2 is just a mirror.

Account Details

  • Bucket: kubequest-images
  • Public URL: https://pub-03b68c7e797949c2add7d7d298478f58.r2.dev/
  • S3 Endpoint: https://ac1c3693224388b2d23c4e01733339ea.r2.cloudflarestorage.com
  • Free tier: 10 GB storage, 1M Class A ops, 10M Class B ops/month, zero egress fees

Setup (One-Time)

1. Install rclone

bash
# Arch Linux
sudo pacman -S rclone

# Ubuntu/Debian
sudo apt install rclone

# Or universal install
curl https://rclone.org/install.sh | sudo bash

2. Configure rclone

Credentials are stored in .env (gitignored). Load them and create the rclone config:

bash
source .env

mkdir -p ~/.config/rclone
cat > ~/.config/rclone/rclone.conf << EOF
[r2]
type = s3
provider = Cloudflare
access_key_id = ${R2_ACCESS_KEY_ID}
secret_access_key = ${R2_SECRET_ACCESS_KEY}
endpoint = ${R2_ENDPOINT}
acl = private
no_check_bucket = true
EOF

Or manually edit ~/.config/rclone/rclone.conf and paste:

ini
[r2]
type = s3
provider = Cloudflare
access_key_id = <from .env>
secret_access_key = <from .env>
endpoint = https://ac1c3693224388b2d23c4e01733339ea.r2.cloudflarestorage.com
acl = private
no_check_bucket = true

3. Verify connection

bash
rclone lsd r2:kubequest-images

Uploading Images

Upload all images in the repo (full sync)

bash
cd /home/vagabond/Development/kubequest

rclone copy . r2:kubequest-images \
  --include "*.png" \
  --include "*.jpg" \
  --include "*.jpeg" \
  --include "*.gif" \
  --include "*.webp" \
  --exclude ".git/**" \
  --exclude "node_modules/**" \
  --transfers 16 \
  --checkers 16 \
  -v

This skips already-uploaded files automatically (rclone checks by size/hash).

Upload a specific directory

bash
# Upload just the CCNA screenshots
rclone copy networking/ccna/cbt-nugget/ r2:kubequest-images/networking/ccna/cbt-nugget/ \
  --include "*.png" \
  --transfers 16 \
  -v

# Upload just the Azure assets
rclone copy azure/ r2:kubequest-images/azure/ \
  --include "*.png" \
  --transfers 16 \
  -v

Upload a single file

bash
rclone copyto path/to/image.png r2:kubequest-images/path/to/image.png

Accessing Images

Any uploaded image is publicly accessible at:

https://pub-03b68c7e797949c2add7d7d298478f58.r2.dev/<path-in-bucket>

Example:

Local:  azure/az-104/01-identity-governance/scott-duffy/assets/entra-pricing-banner.png
URL:    https://pub-03b68c7e797949c2add7d7d298478f58.r2.dev/azure/az-104/01-identity-governance/scott-duffy/assets/entra-pricing-banner.png

The path in R2 mirrors the repo structure exactly.

Using R2 URLs in Markdown

If you want to reference an R2-hosted image instead of a local one:

markdown
<!-- Local (default, keeps working offline) -->
![](assets/my-screenshot.png)

<!-- R2 (for large images you don't want in git) -->
![](https://pub-03b68c7e797949c2add7d7d298478f58.r2.dev/azure/az-104/.../assets/my-screenshot.png)

Useful Commands

bash
# Check what's in the bucket
rclone ls r2:kubequest-images | head -20

# Check bucket size
rclone size r2:kubequest-images

# Dry run (see what would upload without uploading)
rclone copy . r2:kubequest-images \
  --include "*.png" \
  --exclude ".git/**" \
  --exclude "node_modules/**" \
  --dry-run

# Delete a file from R2
rclone deletefile r2:kubequest-images/path/to/image.png

# Sync (upload new + delete removed -- be careful)
rclone sync . r2:kubequest-images \
  --include "*.png" \
  --exclude ".git/**" \
  --exclude "node_modules/**" \
  --dry-run  # remove --dry-run when ready

Credentials

Stored in .env (gitignored):

VariableDescription
CLOUDFLARE_API_TOKENCloudflare API bearer token
CLOUDFLARE_ACCOUNT_IDCloudflare account ID
R2_ACCESS_KEY_IDS3-compatible access key
R2_SECRET_ACCESS_KEYS3-compatible secret key
R2_ENDPOINTS3 endpoint URL
R2_BUCKETBucket name (kubequest-images)

To regenerate S3 credentials: Cloudflare Dashboard > R2 > Manage API Tokens > Create API Token.

Released under the MIT License.