Skip to content
Merged

Dev #99

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions .claude/skills/dev-env/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@ description: Boot, test, or run Rails commands for inbox-web through Docker. Use

The native `bundle` install on this machine does not match `Gemfile.lock`, so `bin/rails`
and `bundle exec` fail directly on the host. Run everything through the dev container
defined by `Dockerfile.dev` + `compose.yaml`.
defined by `Dockerfile.dev` + `compose.dev.yaml`. That file is intentionally NOT a default
Compose name (production's `docker-compose.yml` keeps that slot), so always pass `-f compose.dev.yaml`.

## First time / after Gemfile changes

```bash
docker compose build web # or: docker build -f Dockerfile.dev -t inbox-web-dev .
docker compose -f compose.dev.yaml build web # or: docker build -f Dockerfile.dev -t inbox-web-dev .
```

## Common commands

```bash
docker compose up # web server → http://localhost:3000
docker compose run --rm web bundle exec rspec # full suite
docker compose run --rm web bundle exec rspec spec/lib/unified_storage_service_spec.rb
docker compose run --rm web bin/rubocop
docker compose run --rm web bin/rails console
docker compose run --rm web bin/rails db:prepare
docker compose -f compose.dev.yaml up # web server → http://localhost:3000
docker compose -f compose.dev.yaml run --rm web bundle exec rspec # full suite
docker compose -f compose.dev.yaml run --rm web bundle exec rspec spec/lib/unified_storage_service_spec.rb
docker compose -f compose.dev.yaml run --rm web bin/rubocop
docker compose -f compose.dev.yaml run --rm web bin/rails console
docker compose -f compose.dev.yaml run --rm web bin/rails db:prepare
```

## One-off scripts / reproductions
Expand All @@ -44,6 +45,6 @@ docker run --rm -v "$PWD":/rails -v /tmp/script.rb:/tmp/script.rb \
- The dev SQLite db lives at `storage/development.sqlite3` (a Docker volume, not the host
tree) — an empty host `storage/` is normal, not a sign of lost data.
- `StorageSetting` uses an encrypted column, so `ACTIVE_RECORD_ENCRYPTION_*` keys must be
set in the environment (compose.yaml provides dev defaults).
set in the environment (compose.dev.yaml provides dev defaults).
- Test env uses the `:test` Disk ActiveStorage service; dev/prod use `:unified`. See
`CLAUDE.md` → "File storage" before changing storage code.
14 changes: 9 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,18 @@ Raspberry Pi — no external services required.
Native `bundle` on this machine is currently broken (Gemfile.lock pins gems that aren't
installed for the active Ruby). **Use Docker for anything that boots Rails:**

The dev stack lives in `compose.dev.yaml`. It is **not** a default Compose filename
(that's deliberate — `docker-compose.yml` is the production stack and must stay the
default), so always pass it with `-f`:

```bash
docker compose up # web → http://localhost:3000
docker compose run --rm web bundle exec rspec # full test suite
docker compose run --rm web bin/rubocop # style
docker compose run --rm web bin/rails console
docker compose -f compose.dev.yaml up # web → http://localhost:3000
docker compose -f compose.dev.yaml run --rm web bundle exec rspec # full test suite
docker compose -f compose.dev.yaml run --rm web bin/rubocop # style
docker compose -f compose.dev.yaml run --rm web bin/rails console
```

`compose.yaml` bind-mounts the source (edits are live) and keeps gems + `storage/`
`compose.dev.yaml` bind-mounts the source (edits are live) and keeps gems + `storage/`
(SQLite db + uploads) in named volumes. See `Dockerfile.dev`.

If the native toolchain is fixed, the same commands work without the `docker compose run`
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Development-only image.
# Production deploy uses ./Dockerfile (consumed by Dokku).
#
# Run via: docker compose up
# See: compose.yaml
# Run via: docker compose -f compose.dev.yaml up
# See: compose.dev.yaml
#
# syntax = docker/dockerfile:1

Expand Down Expand Up @@ -32,7 +32,7 @@ RUN apt-get update -qq && \
RUN gem install bundler

# Pre-install gems for faster cold starts. The source tree is bind-mounted
# at runtime (see compose.yaml), and /usr/local/bundle is a named volume,
# at runtime (see compose.dev.yaml), and /usr/local/bundle is a named volume,
# so gems persist across rebuilds and survive bare `compose down`.
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local without "" && \
Expand All @@ -47,5 +47,5 @@ ENV RAILS_ENV=development \

EXPOSE 3000

# Default command — overridden by compose.yaml's `command:` to chain db:prepare.
# Default command — overridden by compose.dev.yaml's `command:` to chain db:prepare.
CMD ["bin/rails", "server", "-b", "0.0.0.0"]
45 changes: 45 additions & 0 deletions compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Local development stack. Production deploy uses Dokku via ./Dockerfile.
#
# docker compose up # web on http://localhost:3000
# docker compose run --rm web bin/rails console
# docker compose run --rm web bundle exec rspec
#
# The source tree is bind-mounted, so host edits are live. Gems live in a
# named volume so they survive `compose down` and aren't reinstalled on every
# boot. App data (SQLite db + uploaded files) lives under storage/ — also a
# named volume so it persists across rebuilds.

services:
web:
build:
context: .
dockerfile: Dockerfile.dev
# db:prepare is idempotent — creates/migrates the SQLite db on first boot.
command: bash -c "bin/rails db:prepare && bin/rails server -b 0.0.0.0"
ports:
- "3000:3000"
volumes:
- .:/rails
- bundle:/usr/local/bundle
- storage:/rails/storage
environment:
RAILS_ENV: development
# ActiveRecord encryption keys — StorageSetting#config_encrypted is encrypted.
# Override with real values (bin/rails db:encryption:init) for persistent data.
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: ${ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY:-devkeydevkeydevkeydevkeydevkey01}
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: ${ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY:-devkeydevkeydevkeydevkeydevkey02}
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: ${ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT:-devkeydevkeydevkeydevkeydevkey03}
# Cloud storage OAuth (optional — configure via Settings > Storage).
# Null values pass the host env through and stay UNSET when not provided —
# don't use ${VAR:-} here, an empty string makes OAuthManager#env_credential
# treat the var as configured.
DROPBOX_CLIENT_ID:
DROPBOX_CLIENT_SECRET:
GOOGLE_DRIVE_CLIENT_ID:
GOOGLE_DRIVE_CLIENT_SECRET:
ONEDRIVE_CLIENT_ID:
ONEDRIVE_CLIENT_SECRET:

volumes:
bundle:
storage: