Cloudflare R2 storage adapter for Ghost.
- Save images in Cloudflare R2
- Supports images, media and files
- Resize images to emulate Responsive Images
- Implements
saveRaw
to force ghost to generate thesrcset
attribute for image cards
- Implements
- Save images and media using UUID as name
- Compress resized images
- Written in TypeScript for maintainability
The adapter can be installed using npm or Docker.
# Starting from the Ghost base directory
mkdir -p content/adapters/storage
cd content/adapters/storage
git clone https://github.com/egeldenhuys/ghost-cloudflare-r2
cd ghost-cloudflare-r2
git checkout v0.1.1
Requires npm
to be installed.
npm install
cp -f ./build/src/index.js ./build/src/index.js.map ./build/src/index.d.ts .
Requires Docker to be installed. This has the advantage of not requiring you to pollute your server with node and npm if you are already using Docker.
./build-in-docker.sh
The storage adapter makes use of the following environment variables:
Environment Variable | Description |
---|---|
GHOST_STORAGE_ADAPTER_R2_ENDPOINT |
Cloudflare R2 Endpoint. Example: https://<account_id>.r2.cloudflarestorage.com |
GHOST_STORAGE_ADAPTER_R2_ACCESS_KEY_ID |
Access Key ID from Cloudflare R2 API Token |
GHOST_STORAGE_ADAPTER_R2_SECRET_ACCESS_KEY |
Secret Access Key from Cloudflare R2 API Token |
GHOST_STORAGE_ADAPTER_R2_BUCKET |
R2 Bucket to use for storage |
GHOST_STORAGE_ADAPTER_R2_DOMAIN |
R2 Custom domain to use for serving content |
GHOST_STORAGE_ADAPTER_R2_UUID_NAME |
Use UUID as name when storing images. May cause issues when used with Responsive Images. Default false . Allowed values true , false |
GHOST_STORAGE_ADAPTER_R2_IMAGES_URL_PREFIX |
URL prefix to use for storing and serving images from R2. Default /content/images/ |
GHOST_STORAGE_ADAPTER_R2_MEDIA_URL_PREFIX |
URL prefix to use for storing and serving media (video) from R2. Default /content/media/ |
GHOST_STORAGE_ADAPTER_R2_FILES_URL_PREFIX |
URL prefix to use for storing and serving files from R2. Default /content/files/ |
GHOST_STORAGE_ADAPTER_R2_CONTENT_PREFIX |
Prefix to apply to all prefixes. Default empty. Must not contain a trailing slash. Example /blog_data |
GHOST_STORAGE_ADAPTER_R2_GHOST_RESIZE |
This needs to be set to false if Image resizing is disabled for Ghost ( env imageOptimization__resize ). Default true |
GHOST_STORAGE_ADAPTER_R2_RESPONSIVE_IMAGES |
Generate an image for each width specified. Uses undocumented Ghost internal logic to get srcset generated. Default false . Allowed values true , false |
GHOST_STORAGE_ADAPTER_R2_SAVE_ORIGINAL |
Save the original unoptimized image. Only applicable if (env imageOptimization__resize ) is set. Default true . Allowed Values true , false |
GHOST_STORAGE_ADAPTER_R2_RESIZE_WIDTHS |
Comma separated list of widths to resize the image when saving. This should match the srcset of your theme and any Ghost overrides. Default 300,600,1000,1600,400,750,960,1140,1200 |
GHOST_STORAGE_ADAPTER_R2_RESIZE_JPEG_QUALITY |
Quality to use when resizing JPEG images. Default: 80 |
GHOST_STORAGE_ADAPTER_R2_LOG_LEVEL |
Log level for the storage adapter. Default info . Allowed values debug , info , warn , error |
GHOST_STORAGE_ADAPTER_R2_SAVE_ORIG_NAME_METADATA |
Save the original file name in the object Metadata under the key original_name . Useful for correlating original images to images with UUID names. Default false . Allowed values true , false |
All environment variables can also be used as keys in the JSON config.
The following Ghost configuration is required to activate the plugin for images
, media
, and files
:
Alternatively they can be specified as environment variables (See docker-compose example below).
"storage": {
"active": "ghost-cloudflare-r2",
"ghost-cloudflare-r2": {
"GHOST_STORAGE_ADAPTER_R2_ENDPOINT": "https://<account_id>.r2.cloudflarestorage.com"
...
},
"media": {
"adapter": "ghost-cloudflare-r2",
"storage_type_media": true
},
"files": {
"adapter": "ghost-cloudflare-r2",
"storage_type_files": true
}
}
The section for media
and files
and be removed if the adapter should not handle those types.
Note: this is an undocumented syntax and might change in future Ghost releases (tested on 5.30.1).
See Configuring Storage Adapters for more details.
If your blog is already live, and you have sent out newsletters with images, then you no longer have control over the image URLs in the emails.
The URLs will be pointing to example.com/content/images/*
but you want to serve them from the CDN cdn.example.com/content/images/*
.
One solution is to use Ghost Redirects (assuming your content has been copied to the CDN):
# Temporary redirect if you might be changing the CDN in the future
302:
^\/content\/images\/(.*)$: https://cdn.example.com/content/images/$1
^\/content\/media\/(.*)$: https://cdn.example.com/content/media/$1
^\/content\/files\/(.*)$: https://cdn.example.com/content/files/$1
If you want the flexibility to later change to a different blog domain or CDN you can set
GHOST_STORAGE_ADAPTER_R2_DOMAIN
to blog.example.com
and use Ghost to redirect old and new image requests to the CDN.
environment:
storage__active: ghost-cloudflare-r2
storage__media__adapter: ghost-cloudflare-r2
storage__media__storage_type_media: true
storage__files__adapter: ghost-cloudflare-r2
storage__files__storage_type_files: true
GHOST_STORAGE_ADAPTER_R2_ENDPOINT: https://<account_id>.r2.cloudflarestorage.com
GHOST_STORAGE_ADAPTER_R2_ACCESS_KEY_ID: xxxxxx
GHOST_STORAGE_ADAPTER_R2_SECRET_ACCESS_KEY: xxxxxx
GHOST_STORAGE_ADAPTER_R2_BUCKET: my-ghost-bucket
GHOST_STORAGE_ADAPTER_R2_DOMAIN: https://cdn.example.com
GHOST_STORAGE_ADAPTER_R2_UUID_NAME: false # optional. Default: false
GHOST_STORAGE_ADAPTER_R2_IMAGES_URL_PREFIX: /content/images/ # optional. Default: /content/images/
GHOST_STORAGE_ADAPTER_R2_MEDIA_URL_PREFIX: /content/media/ # optional. Default: /content/media/
GHOST_STORAGE_ADAPTER_R2_FILES_URL_PREFIX: /content/files/ # optional. Default: /content/files/
GHOST_STORAGE_ADAPTER_R2_CONTENT_PREFIX: '' # optional. Default: ''
GHOST_STORAGE_ADAPTER_R2_GHOST_RESIZE: true # optional. Default: true
GHOST_STORAGE_ADAPTER_R2_RESPONSIVE_IMAGES: false # optional. Default: false
GHOST_STORAGE_ADAPTER_R2_SAVE_ORIGINAL: true # optional. Default: true
# Example widths to get Dawn theme working correctly:
GHOST_STORAGE_ADAPTER_R2_RESIZE_WIDTHS: 300,600,1000,1600,400,750,960,1140,1200 # optional. Default: 300,600,1000,1600,400,750,960,1140,1200
GHOST_STORAGE_ADAPTER_R2_RESIZE_JPEG_QUALITY: 80 # optional. Default: 80
GHOST_STORAGE_ADAPTER_R2_LOG_LEVEL: info # optional. Default: info
GHOST_STORAGE_ADAPTER_R2_SAVE_ORIG_NAME_METADATA: false # optional. Default: false
The tests require a S3 compatible endpoint. A docker-compose file for MinIO has been included to run a local instance for testing.
Also note that the tests will generate random images in /tmp
.
The tests can be refactored and improved.