Orcasite
August 16, 2026 · View on GitHub
This repo specifies the web backend and frontend for the Orcasound app that plays a live audio stream through the user's browser of choice. The backend is an Elixir app using the Phoenix framework. The frontend is built in Next.js.
Contributing
Please check out the CONTRIBUTING doc for tips on making a successful contribution, as well as learning resources!
Quick Start
- Load the Docker Configuration:
docker compose up - Wait for containers to start up
- Navigate to localhost:3000 to view the website
- Navigate to localhost:4000 to access the API server
Note This assumes you have installed docker. If you're running Windows, ensure Docker Desktop is installed and running.
Running in Docker
Docker is the quickest way to get the project up and running, especially if you haven't already set up Erlang/Elixir/Node. The only requirement is that you have docker installed on your machine.
Once you clone the repository, you can just run docker compose in the root directory:
docker compose up
This will build an image locally with all the dependencies you need. It will also pull a pre-built image from Docker Hub for the database, automatically configure everything, and both the Phoenix and Next.js servers. The orcasite page will be accessible at http://localhost:3000 as soon as the web container finishes starting up.
Developing
The quick start setup is great for getting the project up and running, but if you want to do development, you'll want to be able to modify the source code without re-building an entire Docker image.
Setup
This project comes with a devcontainer.json configuration which can be used with VS Code. This takes care of all the docker compose stuff in the background so you don't have to worry about it. When you open the project in VS Code, it should prompt you to start it in a dev container (assuming you've installed docker and the dev containers extension). Once the dev container starts, you can open a new terminal window in VS Code to run commands. See the commands below for how to get everything started.
Tip For a better VS Code experience, copy the example settings file:
cp .vscode/settings.example.json .vscode/settings.jsonThis provides recommended editor settings for this project. You can also see & install recommended extensions by opening the command palette (Cmd+Shift+P) and selecting "Extensions: Show Recommended Extensions".
Getting everything running
Once you have your environment setup via one of the options above, you can start the project. You'll need to run both the Phoenix server and the Next.js server.
Server
In a new terminal session, from the root directory:
> cd server/
> mix deps.get
> mix ecto.setup
> iex -S mix phx.server
Note For future runs, you can skip running the
mixcommands and just start the server withiex -S mix phx.server
The server should soon be available at http://localhost:4000.
UI
Open another terminal session and run these commands to start Next.js:
> cd ui/
> npm i
> npm run dev
Once everything finishes starting up, you'll be able to access the UI at http://localhost:3000.
Database Seeding
The development environment automatically seeds the database with real data from the production server, including detections, audio images, and other records. This happens via background jobs that sync data every 1 minute.
Note: Seeded resources will only work with feeds that match production (i.e., with the same
idandslug). Clear your data withmix ecto.dropand re-initialize it withmix ecto.setupto get the latest data from the production servers.
Seeding on setup
Running mix run priv/repo/seeds.exs will seed all production data from the last hour, and the last 100 bouts, audio images, and detections. This script is included when mix ecto.setup is run.
Configuration
Three environment variables control this behavior:
ENABLE_SEED_FROM_PROD- Enables the seeding feature (default:truein dev)AUTO_UPDATE_SEEDED_RECORDS- Automatically syncs new data every 1 minute (default:truein dev)AUTO_DELETE_SEEDED_RECORDS- Automatically removes all data hourly if the data is older than 7 days, excluding feeds (default:truein dev).
Note If you pull data older than 7 days and the auto-delete setting is turned on, that data will be deleted on the hour. Turn this setting off if you'd like to work with data older than 7 days.
To disable automatic seeding, set these to false in your environment (e.g. in docker-compose.dev.yml) or in server/config/dev.secret.exs:
config :orcasite, enable_seed_from_prod: false
Manual seeding
You can also manually trigger seeding from the /seed page in the UI. This lets you choose whatever resources you want to pull and within a time range of your choosing.
Tests
UI
The new version (v3) is currently under development, rapidly changing, and has no tests yet.
Deployment
For the moment, this app is running in a Heroku instance with mix phx.server. A single prebuilt artifact is promoted through the pipeline — dev to staging to production — rather than each environment building its own.
Configuration is resolved at runtime, not at build time
Because one artifact serves every environment, anything captured while it is built describes the environment it was built in rather than the one serving the request. Environment-specific values must therefore be read at runtime:
- Server.
config/runtime.exsandconfig/prod.exsare evaluated at boot, so they see each app's own config vars. Values reached throughApplication.compile_env/2are the exception: they are captured when the artifact is compiled, so they cannot express per-app configuration, and Elixir validates them against the runtime value at startup. Avoidcompile_envfor anything that differs between environments. The cache adapter incache.exis one, keyed off whetherREDIS_URLis set. - UI. Next inlines
NEXT_PUBLIC_*into the client bundle at build time, soui/.env.productiondeliberately defines none. The GraphQL and socket endpoints resolve same-origin in the browser (client.ts,useSocket.ts); remaining per-environment values are injected per request byruntime-config.tsand read throughruntimeConfig.ts. - Pages.
getStaticPropsandgetStaticPathsrun during the build, so pages showing environment-specific data must not rely on them.listen/index.tsxusesgetServerSidePropsand renders on every request;listen/[feed].tsxprerenders no paths, generating each page on first request and revalidating it after 60 seconds.
Local development builds and runs in a single environment, where inlining is harmless, so ui/.env.development sets NEXT_PUBLIC_* normally.
Smoke checks
Run bin/smoke against an app after promoting to it:
bin/smoke https://live.orcasound.net
It exits non-zero if anything fails. Most checks are self-configuring: they ask the app's own API what it should be showing and confirm the pages agree, so the same command works against any environment. An app serving another environment's data disagrees with its own API, which is the failure this deployment model invites and the reason these exist. The feed comparison runs in both directions, because the failure that prompted this showed up as a page listing extra feeds — production serving a page built from development's list.
Two checks carry knowledge the app cannot supply, and both take an override:
EXPECT_SEED_PAGE=1where seeding is deliberately enabled; elsewhere/seedis expected to be absent.ABSENT_SLUGSlists feeds that exist only in development, so elsewhere they must not resolve. A nonsense slug cannot cover this: a slug that never existed anywhere was never prerendered either.
Nothing runs these automatically. The health-check job in
heroku.yaml fires on GitHub deployment
events, which slug promotions no longer produce.
Console
heroku run -a <app_name> FEED_STREAM_QUEUE_URL="" REDIS_URL="" POOL_SIZE=1 iex -- -S mix
The POOL_SIZE config var is necessary due to the current Postgres db having 20 connections. You can read more about it here.
Logs
Application logs are collected by Mezmo.
Emails
Orcasite uses MJML for email templating. There are a few online MJML renderers, including: mjml.io and grapes.js
API
An API is available using the JSON API spec. For access to available endpoints, navigate to the server's /api/json/swaggerui or /api/json/redoc path for documentation and examples. As an example, you may access the full list of feeds with:
curl -s https://beta.orcasound.net/api/json/feeds \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json"