You can do all of your work on this gem inside Docker, with nothing more than
Docker and just installed on your host —
no system Ruby, no rbenv, no Bundler. This mirrors the setup used by
LocoMotion, pared down to what a
single, dependency-light gem needs.
Dockerfilebuilds aruby:3.3image (the newest Ruby exercised in CI) and runsbundle installagainst the gemspec, Gemfile, andversion.rb. Those three files are copied in before the rest of the source so the dependency layer stays cached and only rebuilds when the dependencies change.docker-compose.ymldefines one service,gem. It mounts the working tree into the container at/app, so edits on the host are live inside the container. Dependencies are baked into the image at/usr/local/bundle(not under/app, so the mount doesn't shadow them); change the Gemfile/gemspec andjust rebuildto reinstall. The container itself just stays alive (tini+tail -f /dev/null); real work happens viadocker compose exec.justfilewraps the commondocker composeinvocations so you rarely type Docker commands directly.
# Build the image and start the container in the background
just up
# Run the test suite
just test
# Run RuboCop (and auto-fix)
just lint
just lint-fix
# Run spec + rubocop together (the default rake task, same as CI)
just check
# Open an IRB console with the gem loaded
just console
# Drop into a shell inside the container
just shell
# Stop the container when you're done
just downRun just with no arguments to list every available command.
| Goal | Command |
|---|---|
| Build the image | just build |
| Rebuild from scratch (no cache) | just rebuild |
| Start the container | just up |
| Re-install gems after editing deps | just bundle |
| Run tests | just test |
| Run Rails integration specs | just test-rails |
| Lint / auto-fix | just lint / just lint-fix |
| Console | just console |
| Shell | just shell |
| Stop | just down |
| Remove containers + volumes | just clean |
- After changing
Gemfileor the gemspec, runjust rebuildto reinstall dependencies into the image. (just bundlere-bundles inside the running container for a quick iteration, but that doesn't persist once the container is recreated —just rebuildis the durable path.) Gemfile.lockis committed, so the image and your host resolve to the same dependency versions. After a dependency change, runjust bundleto refresh the lockfile in the working tree and commit it.- CI tests each Ruby (3.0–3.3) against its own committed lockfile under
gemfiles/ruby_<ver>.gemfile.lock, resolved on that Ruby (a single modern-Ruby lock would pin transitive gems that dropped older Rubies). After a dependency change, regenerate them withrake matrix:lock(runs each version in itsruby:<ver>Docker image) and commit. The Rails integration specs use the same pattern per Rails version (gemfiles/rails_*.gemfile). - The image installs
build-essentialandlibsodium-dev, so native-extension gems compile andrbnacl(the EdDSA/Ed25519 path used to verify Neon Auth JWTs) works out of the box — including the EdDSA verification spec.
We intentionally do not ship a demo app yet. When we want one — for example, a small Rails app that wires Neon Auth in via OmniAuth to exercise the gem end-to-end — LocoMotion's multi-service layout is the model to copy:
- Create the app under
docs/demo/(orexamples/) with its ownDockerfile.demo. - Add a
demoservice todocker-compose.ymlthat:- builds from that Dockerfile,
- mounts the demo app and mounts this gem into the app's vendor path so
the app uses the local working copy (LocoMotion mounts the repo at
vendor/loco_motion-rails; the gem's Gemfile then points at it withgem "neon-api", path: "vendor/neon-api"), - publishes the app's port (e.g.
3000:3000), - loads a gitignored
docs/demo/.env.localfor secrets (Neon API key, OAuth credentials) viaenv_file.
- Add
demo,demo-shell,demo-console, anddemo-testrecipes to thejustfile, each runningdocker compose exec -it demo ....
Keeping the demo app's configuration and secrets inside the demo service (never
in the gem container) is the key discipline — the gem container should never
need an .env file.