Latest Release Latest Nightly Release License
Featured|HelloGitHub ChronoFrame - Self-hosted photo gallery for photographers. | Product Hunt
Languages: English | 中文
A smooth photo display and management application, supporting multiple image formats and large-size image rendering.
- Manage photos online - Easily manage and browse photos via the web interface
- Explore map - Browse photo locations on a map
- Smart EXIF parsing - Automatically extracts metadata such as capture time, geolocation, and camera parameters
- Reverse geocoding - Automatically identifies photo shooting locations
- Multi-format support - Supports mainstream formats including JPEG, PNG, HEIC/HEIF
- Smart thumbnails - Efficient thumbnail generation using ThumbHash
- Nuxt 4 - Built on the latest Nuxt framework with SSR/SSG support
- TypeScript - Full type safety
- TailwindCSS - Modern CSS framework
- Drizzle ORM - Type-safe database ORM
- Multiple storage backends - Supports S3-compatible storage, local filesystem
- CDN acceleration - Configurable CDN URL for faster photo delivery
We recommend deploying with the prebuilt Docker image. View the image on ghcr
Create a .env file and configure environment variables.
Below is a minimal configuration example. For complete configuration options, see Configuration Guide:
# Admin email (required) CFRAME_ADMIN_EMAIL= # Admin username (optional, default Chronoframe) CFRAME_ADMIN_NAME= # Admin password (optional, default CF1234@!) CFRAME_ADMIN_PASSWORD= # Site metadata (all optional) NUXT_PUBLIC_APP_TITLE= NUXT_PUBLIC_APP_SLOGAN= NUXT_PUBLIC_APP_AUTHOR= NUXT_PUBLIC_APP_AVATAR_URL= # Map provider (maplibre/mapbox) NUXT_PUBLIC_MAP_PROVIDER=maplibre # MapTiler access token for MapLibre NUXT_PUBLIC_MAP_MAPLIBRE_TOKEN= # Mapbox access token for Mapbox NUXT_PUBLIC_MAPBOX_ACCESS_TOKEN= # Mapbox unrestricted token (optional, reverse geocoding) NUXT_MAPBOX_ACCESS_TOKEN= # Storage provider (local, s3 or openlist) NUXT_STORAGE_PROVIDER=local NUXT_PROVIDER_LOCAL_PATH=/app/data/storage # Session password (32‐char random string, required) NUXT_SESSION_PASSWORD=
Use the published image on GitHub Container Registry and Docker Hub. Choose the source that works best for your network:
docker pull ghcr.io/hoshinosuzumi/chronoframe:latest
docker pull hoshinosuzumi/chronoframe:latest
Run with customized environment variables:
docker run -d --name chronoframe -p 3000:3000 -v $(pwd)/data:/app/data --env-file .env ghcr.io/hoshinosuzumi/chronoframe:latestCreate docker-compose.yml:
services: chronoframe: image: ghcr.io/hoshinosuzumi/chronoframe:latest container_name: chronoframe restart: unless-stopped ports: - '3000:3000' volumes: - ./data:/app/data env_file: - .env
Start:
docker compose up -d
If
CFRAME_ADMIN_EMAILandCFRAME_ADMIN_PASSWORDare not set, the default admin account is:
- Email:
admin@chronoframe.com- Password:
CF1234@!
- Click avatar to sign in with GitHub OAuth or use email/password login
- Go to the dashboard at /dashboard
- On the Photos page, select and upload images (supports batch & drag-and-drop)
- System will automatically parse EXIF data, generate thumbnails, and perform reverse geocoding
Gallery Photo Detail Map Explore Dashboard
- Node.js 18+
- pnpm 9.0+
# With pnpm (recommended) pnpm install # Or with other package managers npm install yarn install
cp .env.example .env
# 2. Generate migration files (optional) pnpm db:generate # 3. Run database migrations pnpm db:migrate
pnpm dev
App will start at http://localhost:3000.
chronoframe/
├── app/ # Nuxt app
│ ├── components/ # Components
│ ├── pages/ # Page routes
│ ├── composables/ # Composables
│ └── stores/ # Pinia stores
├── packages/
│ └── webgl-image/ # WebGL image viewer
├── server/
│ ├── api/ # API routes
│ ├── database/ # DB schema & migrations
│ └── services/ # Business logic services
└── shared/ # Shared types & utils
# Development (with dependencies build) pnpm dev # Build only dependencies pnpm build:deps # Production build pnpm build # Database operations pnpm db:generate # Generate migration files pnpm db:migrate # Run migrations # Preview production build pnpm preview
Contributions are welcome! Please:
- Fork the repo
- Create a feature branch (git checkout -b feature/amazing-feature)
- Commit changes (git commit -m 'Add some amazing feature')
- Push to branch (git push origin feature/amazing-feature)
- Open a Pull Request
- Use TypeScript for type safety
- Follow ESLint and Prettier conventions
- Update documentation accordingly
This project is licensed under the MIT License.
Timothy Yin
- Email: master@uniiem.com
- GitHub: @HoshinoSuzumi
- Website: bh8.ga
- Gallery: lens.bh8.ga
How is the admin user created?
On first startup, an admin user is created based on CFRAME_ADMIN_EMAIL, CFRAME_ADMIN_NAME, and CFRAME_ADMIN_PASSWORD. The email must match your GitHub account email used for login.
Which image formats are supported?
Supported formats: JPEG, PNG, HEIC/HEIF, MOV (for Live Photos).
Why can’t I use GitHub/Local storage?
Currently only S3-compatible storage is supported. GitHub and local storage support is planned.
Why is a map service required and how to configure it?
The map is used to browse photo locations and render mini-maps in photo details. Currently Mapbox is used. After registering, get an access token and set it to the MAPBOX_TOKEN variable.
Why wasn’t my MOV file recognized as a Live Photo?
Ensure the image (.heic) and video (.mov) share the same filename (e.g., IMG_1234.heic and IMG_1234.mov). Upload order does not matter. If not recognized, you can trigger pairing manually from the dashboard.
How do I import existing photos from storage?
Direct import of existing photos is not yet supported. A directory scanning import feature is planned.
This project was inspired by Afilmory, another excellent personal gallery project.
Thanks to the following open-source projects and libraries: