GrowCast is a Next.js web app for publishing a live garden dashboard with an optional gallery and a protected panel.
GrowCast lets you share your grow in real time. Visitors can view the live stream, some details and media (setup/snapshots/timelapse). Admin users can update all metadata from a web dashboard.
- Live stream embed on the homepage (RTSP camera via MediaMTX (RTSP -> HLS))
- Public grow dashboard
- Markdown support for notes/setup text
- Optional gallery page for snapshots + timelapse video (GrowCast Timelapse plugin)
To see a live demo, visit my instance. The official project site is growcast.0xmarcel.com.
- npm
- Node.js 20 LTS or newer
- An IP camera with RTSP support
- npm (project includes
package-lock.json) - MediaMTX server (to convert RTSP input into HLS output)
- Node.js 20 LTS or newer (assumption based on Next.js 16 setup)
- Docker Engine + Docker Compose plugin (for containerized setup)
- Cloudflare account +
cloudflared(for public tunnel access)
- Clone the repository.
- Install dependencies:
npm install- Create admin credentials:
npm run setup:adminThis script creates .env.local with required admin variables.
Required for admin login:
ADMIN_USERNAME=your_admin_username
ADMIN_PASSWORD_HASH=scrypt$...$...
ADMIN_SESSION_SECRET=at_least_32_chars_random_secretNotes:
ADMIN_PASSWORD_HASHmust use thescrypt$...format.ADMIN_SESSION_SECRETmust be at least 32 characters.- The same
.env.localfile is used bydocker composethroughenv_file.
The repository already includes a production-ready Dockerfile and docker-compose.yml. This is the supported way to run GrowCast in a container.
- Create admin credentials first:
npm run setup:admin- Start the container:
docker compose up --build -d- Open
http://localhost:3000.
Useful commands:
docker compose logs -f growcast
docker compose downWhat gets persisted on the host:
./data->/app/data./extensions->/app/extensions./public/setup->/app/public/setup./public/yourPictures->/app/public/yourPictures
This means grow data, timelapse assets, and uploaded media survive container restarts and image rebuilds.
Optional port override:
- The compose file publishes
${GROWCAST_PORT:-3000}:3000. - If you want a different host port, set
GROWCAST_PORTbefore starting Compose.
Important:
- The container only runs GrowCast. MediaMTX is still a separate service and must be run outside this compose file.
.env.local, media folders, anddata/are intentionally not baked into the image. They are provided at runtime.
npm run devOpen http://localhost:3000.
npm run build
npm run startThis starts the standard Next.js production server. The Docker image builds a standalone bundle automatically during docker compose build.
app/
page.tsx # Public dashboard
gallery/page.tsx # Gallery page
admin/page.tsx # Admin login + dashboard
admin/logout/route.ts # Logout endpoint
api/data/current-grow/ # Current grow JSON endpoint
api/snapshots/[filename]/ # Serves snapshot images
api/timelapse/ # Serves latest timelapse video
components/
dash-pictures.tsx
site-header.tsx
site-footer.tsx
snapshot-gallery.tsx
timelapse-player.tsx
lib/
db.ts # JSON data store + types
admin-auth.ts # Auth/session/rate-limit logic
extension-status.ts # Timelapse plugin file discovery
getSetupImages.ts # Reads public/setup images
scripts/
admin-creator.mjs # Interactive .env.local generator
data/
current-grow.json # Persisted grow data
public/
setup/ # Optional setup photos shown on homepage
yourPictures/ # Optional user uploaded pictures shown on dashboard
extensions/
GrowCast-Timelapse/ # Optional plugin folder (not included)
My App doesnt need much configuration to get started, but i have tested some optimizations for MediaMTX. The default MediaMTX configuration caused issues on iOS devices and significant stuttering on some Windows systems. Below i have included how i configured my MediaMTX server.
hlsAlwaysRemux: true
hlsVariant: fmp4
hlsSegmentCount: 7
hlsSegmentDuration: 1s
hlsPartDuration: 200ms
hlsSegmentMaxSize: 50M
hlsDirectory: ''
hlsMuxerCloseAfter: 60s
paths:
growcam:
source: rtsp://USER:PASSWORD@IP.OF.YOUR.CAM/stream1
sourceProtocol: tcp
sourceOnDemand: no
If you still have issues, make sure your camera is not set to a high frame rate, (i set mine to 15fps, but feel free to try other values).
GrowCast expects a browser-playable stream URL in the admin dashboard. Since some cameras expose RTSP, use MediaMTX to convert RTSP to HLS:
- Configure your RTSP camera (RTSP source looks somewhat like this:
rtsp://<camera-ip>:554/<path>). - Run MediaMTX and create a path that ingests RTSP.
- Use MediaMTX HLS output URL as the stream URL in GrowCast admin (
/admin), for example:http://<mediamtx-host>:8888/<path>/
- Save in GrowCast settings.
This app uses Next.js route handlers and local filesystem storage.
- Primary source:
data/current-grow.json - Read/write logic:
lib/db.ts - If file is missing, default data is generated.
GET /api/data/current-grow- Returns the normalized grow record as JSON
- Uses
Cache-Control: no-store, must-revalidate
GET /api/snapshots/[filename]- Serves image files from
extensions/GrowCast-Timelapse/snapshots
- Serves image files from
GET /api/timelapse- Serves timelapse video from
extensions/GrowCast-Timelapse/timelapse/latest_timelapse.mp4
- Serves timelapse video from
GET /api/data/current-grow- Returns current grow data
- Username + scrypt password hash from env vars
- Signed cookie-based sessions
- In-memory session store
To make the HLS source and app publicly accessible without exposing your home network, publish both services through Cloudflare Tunnel:
- Run GrowCast (example:
http://localhost:3000). - Run MediaMTX (example: HLS endpoint on
http://localhost:8888). - Create tunnel routes with
cloudflared:- One public hostname for GrowCast (example:
growcast.example.com->http://localhost:3000) - One public hostname for MediaMTX HLS (example:
stream.example.com->http://localhost:8888)
- One public hostname for GrowCast (example:
- In GrowCast admin, set
Stream URLto your public MediaMTX HLS URL:https://stream.example.com/<path>/
- Verify both endpoints are reachable through Cloudflare.
Important:
- Keep admin credentials strong (
ADMIN_*env vars).
Cause:
- Missing/invalid
ADMIN_*env variables.
Fix:
- Run
npm run setup:adminand restart the app.
Cause:
extensions/GrowCast-Timelapsefolder missing or no media generated.
Fix:
- Install/run the timelapse plugin and ensure snapshots/timelapse files exist.
Cause:
- Stale page cache after edits.
Fix:
- Restart dev server.
