Skip to content

Media Services

Configure where Deckyard stores uploaded images, and connect an ImageKit photo archive.

Deckyard stores uploaded images in one of two places:

  • Local disk (the default): files in server/uploads/ on the server
  • S3-compatible object storage: AWS S3, Scaleway Object Storage, MinIO, Wasabi, Backblaze B2 or any other S3-compatible store

Separately, you can connect ImageKit as a source to pick images from. ImageKit is not a storage option: an image picked from it is copied into your own storage, local or S3.

Terminal window
# `auto` (default), `s3` or `local`
MEDIA_STORAGE_MODE=auto
  • auto uses S3 when S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET and S3_ENDPOINT are all set, and local disk otherwise. A partial S3 setup prints a warning at startup and falls back to local.
  • s3 always uses S3; the server refuses to start if the four variables are not all set.
  • local always uses local disk.

No configuration is needed. Files are stored in server/uploads/ (change with UPLOADS_DIR) and served at /uploads/<name>-<id>.<ext>.

  • Maximum upload size: 10 MB
  • PNG, JPG and WebP images larger than 3840 × 2160 are scaled down to fit and recompressed in the same format

Local storage suits development and single-server installs; back up the uploads folder together with the database.

Terminal window
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
S3_BUCKET=deckyard-media
S3_ENDPOINT=https://s3.nl-ams.scw.cloud
# Default: nl-ams
S3_REGION=nl-ams
# Optional
S3_PUBLIC_URL=https://media.example.com

S3_ENDPOINT has no default. Examples: https://s3.eu-central-1.amazonaws.com for AWS, https://s3.nl-ams.scw.cloud for Scaleway, https://minio.example.com for MinIO.

The browser uploads directly to the bucket: Deckyard hands out a presigned upload URL (valid for one hour), the browser sends the file, and the server records it. Files are stored as uploads/<year>/<month>/<name>-<id>.<ext>.

  • Maximum upload size: 20 MB
  • Images are stored as uploaded; there is no resizing or format conversion

Social preview images (og:image) are stored through the same storage mode.

Without S3_PUBLIC_URL, image URLs point at the bucket itself (https://<bucket>.<endpoint-host>/...), so the bucket must be publicly readable. Set S3_PUBLIC_URL to serve images from a CDN or custom domain in front of the bucket instead.

Deckyard does not use signed URLs for reading, so a private bucket only works behind such a public URL.

Because the browser uploads straight to the bucket, the bucket needs a CORS rule that allows PUT from your Deckyard origin:

[
{
"AllowedOrigins": ["https://slides.example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]

Older installs used SCW_ACCESS_KEY, SCW_SECRET_KEY, SCW_BUCKET, SCW_REGION, SCW_ENDPOINT and SCW_CDN_URL, and MEDIA_STORAGE_MODE=scaleway. They are still read when the matching S3_* variable is unset, with a warning at startup, and will be removed in the first release after 1 November 2026. Rename them to their S3_* counterparts (SCW_CDN_URL becomes S3_PUBLIC_URL).

Both storage modes accept PNG, JPG, GIF, WebP and SVG.

Images imported from Unsplash and Giphy are always stored on local disk.

ImageKit connects your organization’s photo archive to the image picker.

Terminal window
IMAGEKIT_PUBLIC_KEY=public_...
IMAGEKIT_PRIVATE_KEY=private_...
IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/your-id

All three are required. Optional:

Terminal window
# Folder for files Deckyard uploads to ImageKit
IMAGEKIT_UPLOAD_FOLDER=/deckyard
# Prefix for per-deck tags (default: deck:)
IMAGEKIT_TAG_PREFIX=deck:
# Custom metadata field for ALT text; unset = off
IMAGEKIT_METADATA_FIELD_ALT_SEED=alt_seed
# Files with these tags stay out of the picker (v1.39.0+)
IMAGEKIT_HIDDEN_TAGS=_duplicate,_unsorted

You find the keys and URL endpoint in your ImageKit dashboard.

Files carrying a tag from IMAGEKIT_HIDDEN_TAGS do not appear in the picker, its search results or the tag suggestions. Slides that already use them keep working.

When ImageKit is configured, the image picker opens with Choose image source, listing ImageKit (“Your organisation’s photo archive”) next to Image library and Gradients. In the ImageKit picker you can:

  • Search by name or tags, or use Advanced for a raw search query
  • Choose a Transform: the original, or one of the named transforms deck_slide_full_2x and deck_thumb_2x (define these in ImageKit)
  • Edit tags and ALT text, generate ALT text with AI, and write it back to ImageKit
  • Click Use this image

Deckyard then copies the image (up to 20 MB) into your own storage, and the slide uses that copy. Later changes in ImageKit do not reach the slide.

Copying requires uploads to be enabled. If they are off, the picker says: “This image cannot be used because copying it into your own media requires image uploads to be enabled.”

When ImageKit is configured, three importers store their images in ImageKit instead of your own storage:

  • PowerPoint slides imported as images (folder /converted-slides)
  • PDF pages imported as slide images
  • Images from a Notion import

IMAGEKIT_ONLY=true turns off direct uploads and hides the image library. Because picks from ImageKit are copied into your own storage, which needs uploads, it also blocks new ImageKit picks. Existing images are not affected.

Deckyard has no migration tool. Existing slides keep the URLs they were saved with, so when you move from local to S3 (or between buckets), copy the files and keep the old URLs reachable, or leave the old storage in place for existing images.

  • Check the startup log for a warning about incomplete S3 configuration
  • With S3: check the credentials, the bucket name and the CORS rule for PUT
  • Check the file size (10 MB local, 20 MB S3) and type
  • With S3 and no S3_PUBLIC_URL: check that the bucket is publicly readable
  • Check that the URL in the slide still resolves; moving storage does not rewrite existing URLs
  • ImageKit appears in the picker only when all three IMAGEKIT_* keys are set
  • Picks need uploads to be enabled; check IMAGEKIT_ONLY and UPLOADS_ENABLED