Skip to content
dwotPublic

About

Self-Hosted Cannabis Grow Journal with sensor tracking for AC Infinity Controllers and Ecowitt Soil Sensors

Topics

Resources

Contributing

Stars

85 stars

Watchers

5 watching

Forks

Latest commit

Β 

History

425 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌱 Isley

Self-Hosted Cannabis Grow Journal

Track, trend, and elevate your grow β€” all in one place.

Docker Go License Issues

🌐 Official Site Β· πŸ› Report a Bug Β· πŸ’‘ Request a Feature


Isley Dashboard


Why Isley?

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.


πŸš€ Key Features

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
🌬️ Smoke Reports Rate each harvest after curing with configurable tasting criteria, and see a strain's average across harvests
πŸ“ˆ 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

πŸ› οΈ Coming Soon

  • πŸ”” Alerts and Notifications β€” Set custom thresholds and get notified when conditions go out of range.

⚑ Quick Start

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

🐳 Option 1: PostgreSQL (Recommended)

# 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 -d

Then open http://localhost:8080 β€” default login is admin / isley. You'll be prompted to change your password on first login.


βšͺ Option 2: SQLite (Lightweight / Local)

# 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 -d

Note: SQLite is not recommended for production due to write contention under concurrent load.


πŸ”„ Migrating from SQLite to PostgreSQL

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-db volume before running migration, just in case.

After migration completes, switch back to docker-compose.postgres.yml for your regular deployment.


πŸ” Upgrading (important if you customized ISLEY_PORT)

Older shipped compose files included a hardcoded healthcheck: wget http://localhost:8080/health block on the isley service. A Compose-level healthcheck fully overrides the Dockerfile one, so it always probed port 8080 and ignored your ISLEY_PORT β€” a container running on a custom port was reported unhealthy ("connection refused").

As of v0.2.0 the healthcheck lives only in the Dockerfile and honors ${ISLEY_PORT:-8080}. To pick this up on an existing deployment:

  1. Pull the latest docker-compose.*.yml or delete the healthcheck: block from the isley service in your existing file.
  2. Recreate the container so the change takes effect:
    docker compose up -d --force-recreate
  3. If Isley itself listens on a custom port, set ISLEY_PORT in the container environment (not just a host port remap like "420:8080").

βš™οΈ Configuration

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:

General

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

Database

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

πŸ”Œ API & Integrations

Isley exposes an HTTP API for pushing sensor data from custom devices, IoT hardware, or home automation systems.

Generating an API Key

  1. Log in as an admin and go to Settings β†’ API Settings.
  2. Give the key a name (e.g. the device it's for), click Add Key, and copy it somewhere safe β€” it's shown only once and can't be retrieved later.
  3. Include the key as an X-API-KEY header on all API requests.

You can keep several keys at once β€” add one per device, see when each was last used, and regenerate or revoke any of them individually so rotating one device's key doesn't disturb the others.

Key access (scopes)

Each key has an access level and, separately, permission to push sensor readings. Give a key only what it needs; you can change a key's access later without changing the key itself.

Access Can
Read only read data, e.g. GET /api/overlay
Read and write read, and record plants, activities and measurements (for the upcoming /api/v1)
Admin everything, including settings, deletes, backups, and the endpoints the web UI itself uses
Sensor ingest POST /api/sensors/ingest (can be combined with any level; admin includes it)

New keys default to Read only. Keys created before scoped keys existed are Admin, so existing integrations keep working. A request a key isn't allowed to make gets 403 with "code": "insufficient_scope" and the scope it needed in "required".

API v1

A versioned JSON API at /api/v1. Every Isley instance serves a guide at /api/v1/docs (keys, conventions, times, errors, paging, safe retries and worked examples) and the full OpenAPI 3.1 reference at /api/v1/openapi.yaml, which any OpenAPI tool can load. Settings β†’ API links to the guide.

curl -H "Authorization: Bearer $ISLEY_KEY" https://isley.example/api/v1/instance

GET /api/v1/instance reports the version, the instance time zone (every timestamp in the API carries its offset), enabled features, and what the calling key may do. Errors are {"error": {"code": "...", "message": "..."}} with a stable code.

Available so far:

Endpoint Key
GET /instance read
GET /statuses, GET /metrics, GET /activity-types read
GET /zones, GET /zones/{id} (with sensors and latest readings) read
POST /zones, PATCH /zones/{id}, DELETE /zones/{id} (empty zones only) admin
GET /plants (filter by phase, status_id, zone_id, strain_id), GET /plants/{id} read
GET /plants/{id}/status-history read
POST /plants, PATCH /plants/{id}, PUT /plants/{id}/sensors, POST /plants/{id}/status, PATCH/DELETE /plants/{id}/status-history/{entry_id} write
GET /activities (log: plant_id, zone_id, activity_type_id, from, to, q), GET /activities/{id}, GET /plants/{id}/activities, GET /plants/{id}/measurements read
POST /plants/{id}/activities, POST /activities/bulk, PATCH/DELETE /activities/{id}, POST /plants/{id}/measurements, PATCH/DELETE /measurements/{id} write
GET /plants/{id}/images, GET /images/{id} read
POST /plants/{id}/images (multipart, one image per request), DELETE /images/{id} write
GET /streams (filter by zone_id), GET /streams/{id} read
GET /smoke-criteria, GET /plants/{id}/smoke-reports, GET /smoke-reports/{id}, GET /strains/{id}/smoke-summary read
POST /plants/{id}/smoke-reports, PATCH/DELETE /smoke-reports/{id} write
GET /sensors (filter by zone_id, source), GET /sensors/{id} (with the latest reading), GET /sensors/{id}/data (from, to, bucket = raw or 5m/1h/1d...) read
GET /strains (filter by breeder_id, in_stock, q), GET /strains/{id}, GET /strains/{id}/lineage, GET /strains/{id}/descendants, GET /breeders, GET /breeders/{id} read
POST /strains, PATCH /strains/{id}, POST /strains/{id}/seed-count, PUT /strains/{id}/lineage, POST /breeders, PATCH /breeders/{id} write
DELETE /plants/{id}, DELETE /strains/{id} (no plants), DELETE /breeders/{id} (no strains), PATCH/DELETE /sensors/{id}, POST /streams, PATCH/DELETE /streams/{id} admin

Any POST can send an Idempotency-Key header so a retry doesn't record the same thing twice. Every write is logged on the server with the key's name, so give each script or device its own key.

# Log a watering on plant 12 (activity type 1 is Water; see GET /activity-types)
curl -H "Authorization: Bearer $ISLEY_KEY" -H "Content-Type: application/json" \
     -H "Idempotency-Key: $(uuidgen)" \
     -d '{"activity_type_id": 1, "note": "250 ml"}' \
     https://isley.example/api/v1/plants/12/activities

# Temperature overnight, hour by hour
curl -H "Authorization: Bearer $ISLEY_KEY" \
     "https://isley.example/api/v1/sensors/2/data?from=2026-11-10T20:00&to=2026-11-11T08:00&bucket=1h"

MCP server (AI assistants)

Isley can serve AI assistants such as Claude over the Model Context Protocol at /mcp. It is off until you turn on Enable MCP server in Settings β†’ API. An assistant connects with an API key as a bearer token and gets exactly that key's powers: a Read only key can look things up ("how did the tent do overnight?"), a Read and write key can also start plants, log waterings and feedings, record measurements and change statuses. There are no delete tools.

Claude Code:

claude mcp add --transport http isley https://isley.example/mcp \
  --header "Authorization: Bearer $ISLEY_KEY"

Claude Desktop, through the mcp-remote bridge, in claude_desktop_config.json:

{
  "mcpServers": {
    "isley": {
      "command": "npx",
      "args": ["mcp-remote", "https://isley.example/mcp", "--header", "Authorization:${ISLEY_AUTH}"],
      "env": { "ISLEY_AUTH": "Bearer <your key>" }
    }
  }
}
Tool Key Does
get_overview read growing plants (age, status, zone, days since watered and fed) and each zone's latest readings
list_plants, get_plant read plants by phase, status, zone or strain; one plant with its history, sensors and recent activity
find_strains, get_strain read strains by name, breeder or stock; one strain with its lineage
list_zones, get_sensor_history read zones and sensors; min/avg/max over a time range, bucketed, with on-time for fans and lights
list_reference_data read the status, activity and metric names the write tools take
create_plant, update_plant, set_plant_status write start a plant, change it, move it to a new stage (with harvest weight)
log_activity, record_measurement write log watering, feeding or a note on one or more plants; record a measurement
adjust_seed_count, link_plant_sensors write add or take seeds; set a plant's own sensors

Tools take names where people use names ("Water", "Flower") and times in the instance zone unless they carry an offset. Every write is logged with the key's name.

That covers the plan's resources; an MCP server comes next.

Older endpoints

The endpoints below predate API v1 and stay supported for the integrations that use them: push readings with /api/sensors/ingest, and read with the overlay and sensor data endpoints. New integrations should use API v1, which covers the reads with documented, stable responses. (The web UI also has its own internal endpoints; they aren't an API and can change between versions.)

Ingest Endpoint

POST /api/sensors/ingest β€” needs a key with Sensor ingest (or Admin).

{
  "source": "custom",
  "device": "Arduino Sensor",
  "type": "temperature",
  "value": 25.5,
  "name": "Temperature Sensor 1",
  "new_zone": "Tent 1",
  "unit": "Β°C"
}

source, device, type and value are required; value may be 0. A sensor is matched on source + device + type, so keep those stable and readings append to the same history. name, unit and the zone (zone_id, or new_zone, which finds or creates a zone by name) are applied when the sensor is first created.

Temperature units can be sent as the symbols Β°C / Β°F or as the words celsius / fahrenheit (any case), which are stored as the symbols. Any other unit is stored exactly as sent.

Batches: send a JSON array of up to 60 readings to store several in one request, for example everything a device measured in one cycle:

[
  { "source": "esp32", "device": "node-1", "type": "temperature", "value": 24.7, "unit": "celsius", "new_zone": "Tent 1" },
  { "source": "esp32", "device": "node-1", "type": "humidity",    "value": 58.2, "unit": "%",       "new_zone": "Tent 1" },
  { "source": "esp32", "device": "node-1", "type": "co2",         "value": 612,  "unit": "ppm",     "new_zone": "Tent 1" }
]

A batch is validated as a whole before anything is stored (one bad reading rejects it with 400 and stores nothing), its readings share one timestamp, and the response lists sensor_ids in request order. Each reading counts toward the ingest rate limit of 60 per minute per key; a 429 carries a Retry-After header in seconds.

Use this for: Arduino/ESP32 sensors, Home Assistant, Node-RED, or any off-the-shelf sensor not natively supported by Isley.

Overlay Endpoint

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, with a key that has at least Read only access.

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.

Sensor Data Endpoint

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.


🌑️ Sensor Integration

Isley supports two sensor platforms out of the box, plus a generic HTTP ingest API for custom hardware.

AC Infinity

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:

  1. Go to Settings and enable AC Infinity Sensor Monitoring.
  2. 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.
  3. 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.
  4. 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

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:

  1. Ensure your EcoWitt hub (e.g., GW1000, GW1100, GW2000) is on the same network as your Isley instance.
  2. Go to Settings and enable EcoWitt Sensor Monitoring.
  3. 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.
  4. 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.

Custom Sensors (API Ingest)

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.


πŸ’Ύ Backup & Restore

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

Backing up

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_backup

SQLite:

# 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 start

Restoring

PostgreSQL:

# 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/uploads

SQLite:

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 start

Tip: Automate backups with a cron job. For PostgreSQL, pg_dump can run while the database is online with no downtime.

Built-in Backup & Restore

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.

Creating a backup

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.

What's included

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.

Restoring a backup

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.

Cross-database portability

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 file transfer

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.

Upload size limits

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.

Limitations

  • 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/create endpoint from a cron job.

πŸ”’ Reverse Proxy Examples

Running Isley behind a reverse proxy is recommended for production. This enables HTTPS, custom domain routing, and keeps Isley off a public port.

Nginx

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;
}

Traefik (Docker labels)

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-For headers from your reverse proxy will be used correctly for rate limiting and logging.


πŸ›‘οΈ Production Recommendations

  • 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_SECRET to keep sessions valid across container restarts.
  • Configure a sensor data retention period in Settings to prevent unbounded database growth.
  • Plant photos under /uploads are served without a login, so guest mode and shared links work. Their file names are random and unguessable, but anyone you give a photo's URL to can open it.

πŸ“ Notes

  • 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


⭐ Star History

Star History Chart

About

Self-Hosted Cannabis Grow Journal with sensor tracking for AC Infinity Controllers and Ecowitt Soil Sensors

Topics

Resources

Contributing

Stars

85 stars

Watchers

5 watching

Forks

Releases

Used by

Contributors

Languages