Track, trend, and elevate your grow β all in one place.
π Official Site Β· π Report a Bug Β· π‘ Request a Feature
I built Isley because the tool I wanted didn't exist. Every existing option was either a phone app with a bad UX, a cloud service I didn't trust, or a spreadsheet held together with duct tape. I wanted one self-hosted solution to replace all three:
| Before Isley | With Isley |
|---|---|
| π‘οΈ Vendor apps for sensor graphs | Unified environmental dashboard |
| π Spreadsheets for seeds & harvests | Structured grow journal with charts |
| ποΈ Notepads for feeding & watering history | Timestamped activity logs per plant |
Isley doesn't try to revolutionize your grow β it centralizes your tools so you can focus on what matters.
| Feature | Description | |
|---|---|---|
| π | Grow Logs | Track plant growth, watering, and feeding with custom activity types |
| π‘οΈ | Environmental Monitoring | Real-time sensor data from AC Infinity and EcoWitt, plus custom HTTP ingest |
| πΈ | Image Uploads | Attach photos with captions; add text overlays and watermarks |
| π· | Webcam Integration | Capture periodic snapshots from camera streams via FFmpeg |
| π± | Seed Inventory | Manage strains, breeders, and seed stock with Indica/Sativa and autoflower tracking |
| π | Harvest Tracking | Record harvest dates, yields, and full cycle times |
| π | Graphs and Charts | Visualize sensor data over time with configurable retention windows |
| βοΈ | Customizable Settings | Define custom zones, activities, metrics, and camera streams |
| π | Internationalization | Available in English, German, Spanish, and French |
| π | Guest Mode | Optional read-only access for unauthenticated visitors |
| πΎ | Backup & Restore | Cross-database portable backups with optional image bundling and sensor data filtering |
| π± | Mobile-Friendly | Responsive layout for desktop and mobile |
- π Alerts and Notifications β Set custom thresholds and get notified when conditions go out of range.
Isley runs in Docker and is up in minutes. PostgreSQL is recommended for production; SQLite works great for local testing.
Prerequisites: Docker and docker-compose
# docker-compose.postgres.yml
version: '3.8'
services:
isley:
image: dwot/isley:latest
ports:
- "8080:8080"
environment:
- ISLEY_PORT=8080
- ISLEY_DB_DRIVER=postgres
- ISLEY_DB_HOST=postgres
- ISLEY_DB_PORT=5432
- ISLEY_DB_USER=isley
- ISLEY_DB_PASSWORD=supersecret
- ISLEY_DB_NAME=isleydb
- ISLEY_SESSION_SECRET=change-me-to-a-long-random-string
depends_on:
- postgres
volumes:
- isley-uploads:/app/uploads
restart: unless-stopped
postgres:
image: postgres:16.8-alpine
environment:
- POSTGRES_DB=isleydb
- POSTGRES_USER=isley
- POSTGRES_PASSWORD=supersecret
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres-data:
isley-uploads:docker-compose -f docker-compose.postgres.yml up -dThen open http://localhost:8080 β default login is admin / isley. You'll be prompted to change your password on first login.
# docker-compose.sqlite.yml
version: '3.8'
services:
isley:
image: dwot/isley:latest
ports:
- "8080:8080"
environment:
- ISLEY_PORT=8080
- ISLEY_DB_DRIVER=sqlite
volumes:
- isley-db:/app/data
- isley-uploads:/app/uploads
restart: unless-stopped
volumes:
isley-db:
isley-uploads:docker-compose -f docker-compose.sqlite.yml up -dNote: SQLite is not recommended for production due to write contention under concurrent load.
Already running Isley with SQLite? Isley handles the migration automatically.
Use docker-compose.migration.yml β it mounts both your existing SQLite volume and the new PostgreSQL instance. On startup, if Isley finds existing SQLite data and an empty PostgreSQL instance, it will import everything automatically.
# docker-compose.migration.yml
version: '3.8'
services:
isley:
image: dwot/isley:latest
ports:
- "8080:8080"
environment:
- ISLEY_PORT=8080
- ISLEY_DB_DRIVER=postgres
- ISLEY_DB_HOST=postgres
- ISLEY_DB_PORT=5432
- ISLEY_DB_USER=isley
- ISLEY_DB_PASSWORD=supersecret
- ISLEY_DB_NAME=isleydb
depends_on:
- postgres
volumes:
- isley-db:/app/data
- isley-uploads:/app/uploads
restart: unless-stopped
postgres:
image: postgres:16.8-alpine
environment:
- POSTGRES_DB=isleydb
- POSTGRES_USER=isley
- POSTGRES_PASSWORD=supersecret
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
isley-db:
postgres-data:
isley-uploads:Tip: Back up your
isley-dbvolume before running migration, just in case.
After migration completes, switch back to docker-compose.postgres.yml for your regular deployment.
Most settings are managed through the Settings panel in the app β enable integrations, set device IPs, scan for sensors, and more.
For environment-level configuration, the full reference is below:
| Variable | Default | Description |
|---|---|---|
ISLEY_PORT |
8080 |
Port Isley listens on |
ISLEY_SESSION_SECRET |
(random) | Session encryption key (must be β₯32 bytes) β set this in production |
ISLEY_SECURE_COOKIES |
false |
Set to true to mark session cookies as Secure (cookies only sent over HTTPS). Enable when Isley is fronted by a TLS reverse proxy. |
ISLEY_HSTS_MAX_AGE |
0 |
When > 0, emits a Strict-Transport-Security: max-age=<seconds> header on every response. Only enable when Isley is reachable solely over HTTPS β sending HSTS over plain HTTP poisons clients that previously connected over TLS. A common production value is 31536000 (one year). |
ISLEY_HSTS_INCLUDE_SUBDOMAINS |
false |
When ISLEY_HSTS_MAX_AGE > 0, append ; includeSubDomains to the HSTS header. |
ISLEY_HSTS_PRELOAD |
false |
When ISLEY_HSTS_INCLUDE_SUBDOMAINS=true, append ; preload to the HSTS header. Only opt in if you intend to submit the host to https://hstspreload.org. |
GIN_MODE |
release |
Set to debug for verbose request logging and error details during development |
| Variable | Default | Description |
|---|---|---|
ISLEY_DB_DRIVER |
sqlite |
Database backend: sqlite or postgres |
ISLEY_DB_FILE |
data/isley.db |
SQLite database path |
ISLEY_DB_HOST |
β | PostgreSQL host |
ISLEY_DB_PORT |
5432 |
PostgreSQL port |
ISLEY_DB_USER |
β | PostgreSQL username |
ISLEY_DB_PASSWORD |
β | PostgreSQL password |
ISLEY_DB_NAME |
β | PostgreSQL database name |
ISLEY_DB_SSLMODE |
disable |
PostgreSQL SSL mode β set to require (or verify-full) when connecting to a TLS-enforcing Postgres host |
Isley exposes an HTTP API for pushing sensor data from custom devices, IoT hardware, or home automation systems.
- Log in as an admin and go to Settings β API Settings.
- Click Generate New Key and copy it somewhere safe.
- Include the key as an
X-API-KEYheader on all API requests.
POST /api/sensors/ingest
{
"source": "custom",
"device": "Arduino Sensor",
"type": "temperature",
"value": 25.5,
"name": "Temperature Sensor 1",
"new_zone": "Tent 1",
"unit": "Β°C"
}Use this for: Arduino/ESP32 sensors, Home Assistant, Node-RED, or any off-the-shelf sensor not natively supported by Isley.
GET /api/overlay β Returns a JSON snapshot of all living plants (with linked sensor readings) and grouped sensor data. Designed for live-stream overlays (e.g., OBS browser sources).
Requires the X-API-KEY header.
Response structure:
{
"plants": [
{
"id": 1,
"name": "White Widow #3",
"strain_name": "White Widow",
"zone_name": "Tent A",
"status": "Flower",
"current_day": 42,
"linked_sensors": [
{
"name": "Tent A Temp",
"value": 24.5,
"unit": "Β°C",
"trend": "up",
"source": "ACI",
"type": "temperature"
}
]
}
],
"sensors": { }
}The sensors object groups all sensors by source, zone, and type with their latest readings.
GET /sensorData β Returns historical sensor readings for charting. Requires authentication unless guest mode is enabled.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
sensor |
Yes | Sensor ID |
minutes |
One of minutes or start/end |
Number of minutes of history to return |
start |
One of minutes or start/end |
Start date (YYYY-MM-DD or RFC 3339) |
end |
One of minutes or start/end |
End date (YYYY-MM-DD or RFC 3339) |
Example:
GET /sensorData?sensor=5&minutes=1440
Returns an array of { "id", "sensor_id", "sensor_name", "value", "create_dt" } objects.
Isley supports two sensor platforms out of the box, plus a generic HTTP ingest API for custom hardware.
AC Infinity controllers (UIS series) expose temperature, humidity, and per-port speed data. Isley communicates with the AC Infinity cloud API β your controller must be online and linked to an AC Infinity account.
Setup:
- Go to Settings and enable AC Infinity Sensor Monitoring.
- Click Retrieve Token and enter your AC Infinity account email and password. Isley authenticates once and stores the API token locally β your password is not saved.
- After the token is set, go to Sensors and click Scan and Add AC Infinity Sensors. Isley will discover all controllers and their sensor channels.
- Assign each sensor to a Zone and toggle visibility for sensors you want to display.
Sensor data is polled at the interval configured in Settings β Polling Interval (default 60 seconds). Data appears on the dashboard and can be linked to individual plants for per-plant environmental monitoring.
Note: AC Infinity's API limits password length to 25 characters. If your password is longer, only the first 25 characters are used during authentication.
EcoWitt weather stations and soil sensors expose data over a local HTTP API on your network. No cloud account is required β Isley communicates directly with the EcoWitt hub.
Setup:
- Ensure your EcoWitt hub (e.g., GW1000, GW1100, GW2000) is on the same network as your Isley instance.
- Go to Settings and enable EcoWitt Sensor Monitoring.
- Go to Sensors and click Scan and Add EcoWitt Sensors. Enter the IP address of your EcoWitt hub (e.g.,
192.168.1.50) and click Scan. - Isley will discover soil moisture and indoor temperature/humidity channels. Assign zones and set visibility as needed.
EcoWitt data is polled at the same Polling Interval as AC Infinity. Multiple EcoWitt hubs are supported β scan each one individually.
For hardware not natively supported (Arduino, ESP32, Home Assistant, etc.), use the HTTP ingest endpoint documented in the API & Integrations section above. Any device that can make an HTTP POST can push sensor data into Isley.
Isley stores data in two Docker volumes that should be backed up regularly.
| Volume | Contents | Path inside container |
|---|---|---|
postgres-data (or isley-db for SQLite) |
Database β all plants, sensors, settings, and history | /var/lib/postgresql/data (Postgres) or /app/data (SQLite) |
isley-uploads |
Plant photos, stream snapshots, and logo images | /app/uploads |
PostgreSQL:
# Dump the database to a SQL file
docker exec isley-postgres-1 pg_dump -U isley isleydb > isley_backup_$(date +%Y%m%d).sql
# Back up the uploads volume
docker cp isley-isley-1:/app/uploads ./isley_uploads_backupSQLite:
# Copy the database file (stop the container first to avoid corruption)
docker compose -f docker-compose.sqlite.yml stop
docker cp isley-isley-1:/app/data ./isley_data_backup
docker cp isley-isley-1:/app/uploads ./isley_uploads_backup
docker compose -f docker-compose.sqlite.yml startPostgreSQL:
# Restore the database from a SQL dump
cat isley_backup_20260315.sql | docker exec -i isley-postgres-1 psql -U isley isleydb
# Restore uploads
docker cp ./isley_uploads_backup/. isley-isley-1:/app/uploadsSQLite:
docker compose -f docker-compose.sqlite.yml stop
docker cp ./isley_data_backup/. isley-isley-1:/app/data
docker cp ./isley_uploads_backup/. isley-isley-1:/app/uploads
docker compose -f docker-compose.sqlite.yml startTip: Automate backups with a cron job. For PostgreSQL,
pg_dumpcan run while the database is online with no downtime.
Isley includes a built-in backup system accessible from the Settings > Backup tab. It produces portable .zip archives that work across database backends β you can back up a SQLite instance and restore it onto PostgreSQL, or vice versa.
Navigate to Settings > Backup and choose your options before clicking Create Backup:
| Option | Values | Effect |
|---|---|---|
| Sensor History | All, Last 7/30/90 days, None | Controls how much sensor data is included. Excluding sensor data keeps backups small and fast. |
| Include Images | On / Off | Bundles uploaded plant photos and stream snapshots into the archive. Can significantly increase backup size. |
Backups run asynchronously β you can navigate away and return later. Completed archives appear in the Available Backups table for download or deletion.
A backup archive contains a backup.json file with a full export of all application data (plants, strains, breeders, zones, activities, metrics, sensors, sensor readings, status history, measurements, images metadata, and streams), plus an optional uploads/ directory with image files. The manifest records the Isley version, source database driver, creation timestamp, and the options used.
Upload a .zip archive in the Restore Backup section. The restore process replaces all existing data and runs through several phases: clearing existing tables, inserting data, resetting sequences (PostgreSQL), and extracting image files. A progress indicator shows the current phase and table-level progress throughout.
For SQLite users, a Skip sensor data option is available to dramatically speed up imports when sensor history isn't needed.
Warning: Restoring a backup is destructive β it replaces all data in the current instance. Sensor polling is paused automatically during the restore.
Backups are stored as JSON, not SQL, so they are database-agnostic. A backup created on SQLite can be restored onto PostgreSQL and vice versa. Foreign keys, auto-increment sequences, and driver-specific optimizations are handled automatically during restore.
SQLite users have an additional option under SQLite File Transfer: download or upload the raw .db database file directly. This is the fastest way to clone or migrate a SQLite instance since it bypasses row-by-row import entirely. The uploaded file is validated by checking the SQLite magic header before replacing the active database.
The maximum upload size defaults to 5 GB and applies to both backup archive uploads and SQLite file uploads. It also caps the total bytes extracted from a backup archive to defend against decompression bombs. You can adjust this limit in the Restore Backup section of Settings β the minimum is 100 MB.
- Full backups only β every backup is a complete export; incremental or delta backups are not supported.
- SQLite restore performance β importing large sensor datasets into SQLite is significantly slower than PostgreSQL due to SQLite's single-writer architecture. Use the Skip sensor data toggle or the SQLite File Transfer feature for faster restores.
- Memory usage β backup archives are read into memory during restore. Very large backups (multi-GB with images) will temporarily consume a corresponding amount of RAM.
- No scheduled backups β backups must be triggered manually from the UI or API. For automated backups, use the Docker volume approach described above or call the
POST /settings/backup/createendpoint from a cron job.
Running Isley behind a reverse proxy is recommended for production. This enables HTTPS, custom domain routing, and keeps Isley off a public port.
server {
listen 443 ssl;
server_name isley.example.com;
ssl_certificate /etc/letsencrypt/live/isley.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/isley.example.com/privkey.pem;
client_max_body_size 20M; # allow image uploads
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name isley.example.com;
return 301 https://$host$request_uri;
}Add these labels to the isley service in your compose file:
services:
isley:
# ... existing config ...
labels:
- "traefik.enable=true"
- "traefik.http.routers.isley.rule=Host(`isley.example.com`)"
- "traefik.http.routers.isley.entrypoints=websecure"
- "traefik.http.routers.isley.tls.certresolver=letsencrypt"
- "traefik.http.services.isley.loadbalancer.server.port=8080"Note: Isley ships with trusted proxy configuration for private RFC-1918 ranges, so
X-Forwarded-Forheaders from your reverse proxy will be used correctly for rate limiting and logging.
- Use Docker with PostgreSQL behind a reverse proxy (Nginx, Traefik) for TLS termination and clean URL routing.
- Back up these volumes on a regular schedule β see Backup & Restore above.
- Set
ISLEY_SESSION_SECRETto keep sessions valid across container restarts. - Configure a sensor data retention period in Settings to prevent unbounded database growth.
- Isley is in active development π§ β breaking changes may occasionally occur between releases.
- Found a bug or have a feature request? Open an issue β contributions welcome.
π For screenshots, feature highlights, and the latest news: dwot.github.io/isley
