Snapshot reads configuration from environment variables and .env.
Start by copying the example file:
cp .env.example .envPowerShell:
Copy-Item .env.example .envGenerate a strong API key:
python -c "import secrets; print(secrets.token_urlsafe(48))"Use the generated value as API_KEY.
| Variable | Default | Description |
|---|---|---|
ENVIRONMENT |
development |
Set to production for stricter startup validation |
HOST |
0.0.0.0 |
Bind address used by Uvicorn |
PORT |
3000 |
HTTP port |
BASE_URL |
http://localhost:3000 |
Public base URL returned after uploads |
API_KEY |
required | API key required for upload, list, and delete |
STORAGE_DIR |
/app/data/uploads in Docker |
Directory where uploaded files are stored |
PUBLIC_DIR |
public |
Directory for optional static public assets |
MAX_FILE_SIZE_BYTES |
52428800 |
Maximum accepted upload size |
UPLOAD_CHUNK_SIZE_BYTES |
1048576 |
Streaming read/write chunk size |
FILE_ID_LENGTH |
10 |
Length of generated public file IDs |
ALLOWED_EXTENSIONS |
see .env.example |
Comma-separated allowed extensions |
ALLOWED_MIME_TYPES |
see .env.example |
Comma-separated allowed MIME types |
CORS_ORIGINS |
* |
Comma-separated CORS origins |
ALLOWED_HOSTS |
* |
Comma-separated trusted host names |
HEALTHCHECK_HOST |
localhost |
Host header used by the Docker healthcheck |
ENABLE_DOCS |
true |
Enables /docs, /redoc, and /openapi.json |
HSTS_ENABLED |
false |
Enables Strict-Transport-Security for HTTPS deployments |
RATE_LIMIT_ENABLED |
true |
Enables rate limiting |
RATE_LIMIT_REQUESTS |
120 |
Requests allowed per rate-limit window |
RATE_LIMIT_WINDOW_SECONDS |
60 |
Rate-limit window length |
LOG_LEVEL |
INFO |
Python logging level |
LOG_FILE |
empty in Docker | Optional rotating log file path. Empty disables file logging |
SHAREX_CONFIG_ENABLED |
true |
Generates a ShareX .sxcu file on startup |
EMBED_ENABLED |
true |
Emits Open Graph and Twitter Card metadata on the HTML page |
EMBED_PROVIDER_NAME |
Snapshot |
Provider name used in og:site_name and the {provider} template variable |
EMBED_TITLE_TEMPLATE |
{filename} |
Template for og:title / twitter:title |
EMBED_DESCRIPTION_TEMPLATE |
Uploaded with Snapshot |
Template for og:description / twitter:description |
EMBED_THEME_COLOR |
#5865F2 |
Accent and theme-color. Must match ^#[0-9A-Fa-f]{6}$ |
EMBED_LOCALE |
en_US |
Value used for og:locale and the page language |
TIMEZONE |
UTC |
IANA time zone (e.g. Europe/Berlin) used to display {created_at} and the on-page upload time |
Page branding (brand name/logo, <title> template, on-page description,
footer, and custom CSS) is fixed in code and is not configurable.
GET /{id} always returns a clean, extension-less URL
(https://img.example.com/<id>) that renders a responsive HTML preview page with
Open Graph and Twitter Card metadata for Discord and other platforms, with the
Download button, Original link, and file metadata block always shown. The raw
file is always available at https://img.example.com/raw/<id>.<ext> — use that
URL directly if you only want the file, with ?download=1 forcing a download
via Content-Disposition: attachment. Cache-Control headers are fixed:
public, max-age=31536000, immutable for raw files and public, max-age=300
for the HTML page.
GET /{id}.{ext} is a compatibility route for old extension-based links: it
looks up the file by id (ignoring the extension you pass) and, if found,
responds with 301 Moved Permanently to the clean /{id} URL. Unknown ids
return 404. There is no separate delete route for this path — delete via
DELETE /raw/{id}.{ext}.
Only the embed metadata (EMBED_*) and TIMEZONE are configurable — the
delivery mode and cache headers are not.
The embed title/description templates accept only these variables:
{id} {filename} {extension} {content_type} {size} {size_human} {created_at} {provider}
{created_at} and the on-page upload time are formatted as a human-readable
date/time in EMBED_LOCALE and converted to TIMEZONE (falling back to
en_US / UTC if either is invalid or unrecognized). Startup fails fast if
TIMEZONE is not a valid IANA time zone name.
Unknown variables (or attribute-style expressions such as {file.__dict__}) are
rejected at startup, and every rendered value is HTML-escaped.
The release version check uses fixed internal defaults instead of environment variables. Snapshot checks in the background every 10 minutes, keeps the cached result for 10 minutes, applies a 60 second cooldown to all GitHub fetch attempts, and uses a 3 second request timeout.
Use stricter values for public deployments:
BASE_URL=https://img.example.com
CORS_ORIGINS=https://example.com
ALLOWED_HOSTS=img.example.com,localhost,127.0.0.1
HEALTHCHECK_HOST=localhost
ENABLE_DOCS=false
HSTS_ENABLED=true
ENVIRONMENT=productionKeep these files and directories private:
.envdata/*.log
They are already ignored by .gitignore.
STORAGE_DIR=/app/data/uploads points to the container path. The repository
Compose file and README docker run quickstart mount ./data to /app/data:
./data/uploads -> /app/data/uploads
./data/snapshot.sxcu -> /app/data/snapshot.sxcu
Outside Docker, Snapshot writes snapshot.sxcu to the current working
directory. In Docker, it writes /app/data/snapshot.sxcu, which appears on the
host as data/snapshot.sxcu.
Manual files in data/uploads are supported when their filename stem contains
only letters, numbers, dots, underscores, and hyphens. Examples:
summer-2026.png
dashboard_01.webp
LOG_FILE= disables rotating file logs. This is the recommended Docker default
because logs are available through:
docker compose logs -f
docker compose logs --no-color > snapshot.logIf you explicitly want file logs, mount a log directory and set LOG_FILE:
volumes:
- ./data:/app/data
- ./data/logs:/app/logsLOG_FILE=/app/logs/snapshot.log