← Back to gregblotzer.com

PlantCare

Go · Anthropic · Gemini · Ollama · Pulumi · AWS

A self-hosted houseplant care assistant. Identify any plant by name or photo, get a structured AI-powered care plan, and export care reminders to Apple Calendar, Outlook, or Google Calendar. Built in Go as a single static binary that compiles to a roughly 15 MB distroless Docker image.

The Go implementation was built with AI-assisted development under architectural direction. The interfaces, data contracts, security requirements, and deployment targets were designed and specified before implementation; the code was generated, reviewed, and iterated on collaboratively with Claude Code.

The AI layer is built around a PlantIdentifier interface. Three backends are available at runtime: Anthropic Claude Haiku 4.5, Google Gemini 2.0 Flash, and local Ollama. The active backend is selected in the Settings UI without restarting the application — API keys are stored AES-256-GCM encrypted in SQLite and never returned to the client.

Plant identification works by name lookup by default. When a name is submitted, it is validated against the GBIF species database before any AI call is made — catching typos and rejecting non-plant input early rather than spending an AI invocation on bad input. Photo-based identification invokes the vision-capable path of whichever backend is active.

The API requires bearer token authentication when configured. Rate limiting runs per client IP using a token bucket with a stricter limit on AI endpoints to prevent cost-driving abuse. The limiter only trusts X-Forwarded-For from CIDRs in a configured trusted proxies list and fails closed — requests whose source cannot be identified share a throttled bucket rather than bypassing limits.

Image uploads are validated by magic bytes against an allowlist of MIME types, not by filename. Request body sizes are capped per endpoint. Plant names are limited to 200 characters and screened against GBIF before reaching any AI backend. HTTP security headers are set on all responses.

PlantCare supports three deployment targets from the same binary. Docker Compose runs locally with SQLite for storage. AWS Lambda uses a separate entrypoint and DynamoDB via a PlantStore interface — the storage backend is selected at runtime with no code changes. A local Kubernetes deployment using kind mirrors the Docker Compose model with a single Deployment and an optional in-cluster Ollama pod.

Cloud infrastructure is provisioned with Pulumi in Go: Lambda function with Function URL, DynamoDB table, S3 upload bucket, IAM role, and CloudWatch log group. The architectural decision to use a single Deployment rather than a pod-per-backend routing design is documented in ADR 004.

Go (AI-assisted), Anthropic API (Claude Haiku 4.5), Google Gemini 2.0 Flash, Ollama, GBIF API, Pulumi (Go), AWS (Lambda, DynamoDB, S3, CloudWatch), Docker, SQLite, kind.

github.com/dablotz/plant-care — README, ADRs, and API reference.