# The iNaturalist Rails app: how a citizen science site is put together

> The inaturalist repository is the Rails application behind iNaturalist.org, and it is one of the more instructive open source web apps to read because it does several hard things at once: media processing, taxonomy, search and a large multi-language front end. Its README is short, so the interesting material is in the compose file, the Dockerfile and the package manifest.

**inaturalist/inaturalist** — The Rails app behind iNaturalist.org

- Repository: https://github.com/inaturalist/inaturalist
- Website: http://www.inaturalist.org
- Stars: 866 · Forks: 234
- Language: JavaScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/inaturalist-inaturalist

## A Rails app with a JavaScript front end

The repository describes itself as the open source Rails app behind iNaturalist.org, and the layout confirms a hybrid. There is the standard Rails skeleton with app, config, db, lib, spec, test and public directories, a Gemfile and Gemfile.lock, a Capfile and a Rakefile, and a config.ru, and there is also a JavaScript toolchain with package.json, package-lock.json, a gulpfile.js, a bower.json, a .bowerrc, a babel.config.js, a jest.config.js and a tsconfig.json. So the application is Rails on the server with a substantial asset pipeline and a separately built front end, not a Rails app with a few compiled files. The mix of older and newer tooling in one tree, Bower alongside Webpack alongside Babel, is the clearest signal that this is a codebase that has grown rather than been designed fresh.

## The compose file is the architecture diagram

The docker-compose file is the most informative document in the repository because it names every backing service and how they are configured. There is Redis on port 6379 with a named volume for its data. There is Elasticsearch 8.15.3 configured as a single node with security disabled, with a snapshot path, heap set to 2 GB on both sides, and a command that installs the analysis-kuromoji plugin on first start, which is the Japanese text analysis plugin. There is Memcached on port 11211. There is PostgreSQL with PostGIS, the postgis/postgis image on the alpine variant, with the database user, password and host authentication method set to md5 and the server started with statement logging to stderr and md5 password encryption. And there is Qdrant on its gRPC and HTTP ports with an API key set through the environment and a production configuration file mounted from the compose file.

## Why five data stores are running at once

The service list reads like a search and recommendation stack, and each piece has a job the others cannot do. PostgreSQL with PostGIS is the system of record and the geographic index, since observations carry coordinates. Elasticsearch handles text search across the enormous corpus of species names, descriptions and observation comments, with the Japanese analysis plugin indicating that the search corpus includes non-English content. Redis is the cache and the queue substrate, memcached is the second-level object cache, and Qdrant is the vector database, which is what a modern image or text similarity feature would query. Putting a vector store alongside a full text search engine and a spatial database is the shape of a system that has added machine learning features to an existing catalogue rather than started with them.

## The Dockerfile and the native dependencies

The application image starts from ruby:3.3-bookworm with RAILS_ENV set to development, and the apt-get line is a list of what the app needs from the operating system: nodejs, postgresql-client-15, the GEOS and GDAL libraries and their binaries, proj-bin and libproj-dev for coordinate projection, exiftool for reading image metadata, ffmpeg for media, libcurl with its OpenSSL development files, zip, and a JDK. A second layer adds the image codec libraries: libjpeg, libpng, libtiff, libwebp, libgif, libheif, freetype, lcms2, libxml2, libltdl, libde265, ghostscript and imagemagick. libheif is built from source in a separate stage with cmake because the distribution version is not what the app needs, and the result is registered with ldconfig. A non-root user named inaturalist is created, and the code directory is chowned to it.

## The Makefile targets you actually use

The Makefile is short and defines the day-to-day workflow in terms of the compose file.

```bash
docker compose build
```

```bash
docker compose up -d es memcached redis pg
```

The services target builds and starts exactly the four backing services in parallel, leaving the application itself out, which is the shape of a development workflow where you run the Rails process on the host against containers. The services-api and services-vision-api targets do the same thing but compose in the compose files from the separate iNaturalistAPI and inatVisionAPI repositories, with the path overridable through an environment variable, which tells you the API and the computer vision service are separate projects that share a network. There is also a clean target that takes the containers down, removes orphans, prunes images and prunes volumes, and a stop target that stops the containers without removing them.

## The JavaScript side and how it is tested

The package manifest is for the asset toolchain rather than for a published library, and the scripts describe the pipeline: webpack with a config file for the production build, a watch mode that sets NODE_ENV to development, eslint as a local binary, a typecheck that runs tsc with no emit, jest for unit tests, and Playwright for end-to-end tests. The Playwright configuration lives at spec/e2e/playwright/playwright.config.ts, and the variants are the usual set, a headed run, a UI mode, a CI run producing an HTML report, and a command to open the report afterwards. So the end-to-end suite drives a real browser against the Rails application, which for an app whose main content is photographs and maps is the only meaningful way to test it. The dependency list is also revealing: d3 for the charts, billboard.js, chroma-js for colour work, exifreader, heic-to, h3-js for hexagonal geospatial indexing, markerclustererplus for clustering markers on a map, and immutability-helper for the state handling.

## Issues go to the forum, security goes to email

The README is unusually explicit about where to take problems. It asks you not to open GitHub issues for bug reports or feature requests, because the work is tracked internally and inbound requests are triaged through the iNaturalist Forum, with separate categories for bug reports and for feature requests. The reasoning given is that the Forum gets a faster response and lets you hear from other community members with similar experiences. Security vulnerabilities that need confidential communication should go by email to a dedicated address, and the project says it does not offer bug bounties but does appreciate responsible disclosure. For contributors there is a link to a contributing code guide and a note that the engineering team is small, so a response might be slow.

## Where this application is the wrong thing to copy

Four limits apply. The database is enormous and the schema is specific to a biodiversity taxonomy, so the useful part of this repository is the topology rather than the models. The repository is a community platform with a governance and moderation function that a commercial copy would have to rebuild, and the project explicitly steers people who want to run their own instance toward the iNaturalist Network instead of a fork. The licence of the code is MIT, but the licence of the data is separate, and the Time Zone Geometries attribution shows how that works in practice, since using that feature with the recommended source data pulls in Timezone Boundary Builder under the Open Database License. And the front-end toolchain shows its age, with Bower and a bower.json alongside Webpack, so a new project would not copy the asset setup.

## Conclusion

Use this repository as a reference when you are building something like a media-heavy catalogue with geographic search, since the compose file and Dockerfile show a real production topology rather than a tutorial one. Do not fork it to run your own copy of the service, because the project asks you to join the iNaturalist Network instead, and the community data is the part that cannot be cloned. Verify first which services you actually need, since the compose file brings up five, that your team can live with the licence of the data you ingest, particularly the Time Zone Geometries attribution to Timezone Boundary Builder under the ODbL, and that you read the contributing guide before sending a patch, since the project routes issues and feature requests to its own forum rather than to GitHub.

## FAQ

### What is in the inaturalist repository?

It is the Rails application behind iNaturalist.org, with the usual app, config, db, lib and test directories alongside a substantial JavaScript asset pipeline built with webpack, gulp, Babel and Bower, and a spec directory holding RSpec, Cucumber features and a Playwright end-to-end suite.

### Which backing services does the iNaturalist app use?

The compose file brings up five: Redis on port 6379, Elasticsearch 8.15.3 with security disabled and the analysis-kuromoji plugin installed, Memcached on port 11211, PostgreSQL with PostGIS, and Qdrant as the vector database. There are named volumes for the data of each.

### How do I run the iNaturalist services locally?

The Makefile has a services target that runs docker compose build --parallel es memcached redis pg and then docker compose up -d for the same four, leaving the Rails application to run on the host. Separate targets services-api and services-vision-api compose in the files from the iNaturalistAPI and inatVisionAPI repositories.

### Where should I report bugs and feature requests for iNaturalist?

Not on GitHub. The README asks you to use the iNaturalist Forum, which has separate categories for bug reports and feature requests, because the team triages inbound requests there. Security vulnerabilities needing confidential handling should go to the help+security address, and there is no bug bounty.

### What licence does iNaturalist use, and what about the data?

The code is MIT, with an MIT-LICENSE file at the root. Data has separate terms, and the README notes that using the Time Zone Geometries feature with the recommended source data includes information from Timezone Boundary Builder, made available under the Open Database License.

## Sources

- [inaturalist/inaturalist on GitHub](https://github.com/inaturalist/inaturalist)
- [Issues](https://github.com/inaturalist/inaturalist/issues)
- [License: MIT](https://github.com/inaturalist/inaturalist/blob/main/LICENSE)
- [Project website](http://www.inaturalist.org)
- [README](https://github.com/inaturalist/inaturalist/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/inaturalist-inaturalist
