Files

234 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Rails + PostgreSQL + Docker – začetna predloga
Predloga za razvojno okolje Ruby on Rails v Dockerju. Vsebuje samo infrastrukturo (Docker, Compose, okoljske spremenljivke, ignore datoteke). Rails aplikacijo ustvariš znotraj kontejnerja z `rails new`, zato na računalniku ne potrebuješ nameščenega Rubyja.
## Sklad
| Komponenta | Različica | Opomba |
|---|---|---|
| Ruby | 4.0.9 | uradna slika `ruby:4.0.9-slim` (minimalen Debian) |
| Ruby on Rails | zadnja stabilna | namesti se z `gem install rails` |
| PostgreSQL | 18 (`PG_MAJOR`) | uradna slika `postgres:18`; klient iz repozitorija PGDG |
| JavaScript | importmap | privzeto v Rails 8; Node.js ni potreben (glej [Če potrebuješ Node.js](#če-potrebuješ-nodejs)) |
| Docker Compose | v2 | servisa `app` in `db` |
## Datoteke
| Datoteka | Namen |
|---|---|
| `Dockerfile` | slika za razvoj: Ruby, sistemske knjižnice (`build-essential`, `git`, `libpq-dev`, `libyaml-dev`, `pkg-config`, `postgresql-client-18`), repozitorij PGDG, uporabnik `app` (UID 1000) |
| `docker-compose.yml` | servis `app` (Rails na vratih 3000) in servis `db` (PostgreSQL) ter volumna `bundle` in `pgdata` |
| `.env.example` | predloga okoljskih spremenljivk (različica PostgreSQL in dostop do baze); kopiraš jo v `.env` |
| `.gitea/template` | seznam datotek, v katerih Gitea ob ustvarjanju repozitorija iz predloge zamenja spremenljivke z imenom repozitorija |
| `.dockerignore` | v sliko ne kopira skrivnosti, `.git`, dnevnikov in začasnih datotek |
| `.gitignore` | v Git ne shrani `.env`, ključev za credentials, dnevnikov ipd. |
## Zasnova
- **Osnova je `ruby:4.0.9-slim`**, enako kot v Dockerfile, ki ga ustvari Rails. Je precej manjša od polne slike, glibc pa omogoča uporabo vnaprej prevedenih gemov (nokogiri, pg ...). Razvojna orodja, ki jih `slim` nima, so dodana z `apt-get`:
- `build-essential` in `pkg-config` za prevajanje nativnih gemov,
- `libpq-dev` za gem `pg`,
- `libyaml-dev` za gem `psych`,
- `git` za geme, nameščene iz Git repozitorijev,
- `postgresql-client-<PG_MAJOR>` za `rails dbconsole` in `db:schema:dump` v formatu SQL.
- **Klient PostgreSQL je iz uradnega repozitorija PGDG** (apt.postgresql.org), ne iz Debiana. Debian ima navadno starejšo glavno različico, `pg_dump` pa zavrne izvoz iz novejšega strežnika. Različico za strežnik (`postgres:<PG_MAJOR>`) in klient določa ena spremenljivka `PG_MAJOR` v `.env` (privzeto 18). Compose jo preda Dockerfile kot build argument. Po spremembi `PG_MAJOR` zgradi sliko znova (`docker compose build`). Pri prehodu na novo glavno različico strežnika obstoječi podatki v volumnu `pgdata` niso združljivi, zato jih je treba izvoziti in uvoziti (ali volume izbrisati).
- **Geme hrani volume `bundle`**, ki je priklopljen na `/usr/local/bundle`. To je `GEM_HOME` uradne Ruby slike in je že v `PATH`. `gem install` in `bundle install` zato pišeta v isto mapo, gemi pa se ohranijo med zagoni kontejnerja.
- **`BUNDLE_PATH` namenoma ni nastavljen.** Z njim bi `bundle install` geme nameščal v drugo mapo kot `gem install`, izvršljive datoteke (`rails`, `rake`) pa ne bi bile v `PATH`.
- **Kontejner teče kot uporabnik `app` (UID 1000)**, zato so datoteke, ki jih ustvari (npr. `rails new`, migracije), na računalniku v lasti tvojega uporabnika. Če ima tvoj uporabnik drug UID (preveri z `id -u`), ga spremeni v `Dockerfile` in v `docker-compose.yml` (`user:`).
- **Koda je priklopljena z `.:/app`**, zato se spremembe takoj vidijo v kontejnerju, brez ponovne gradnje slike.
- **Podatke baze hrani volume `pgdata`**, zato ostanejo tudi po `docker compose down`. Priklopljen je na `/var/lib/postgresql` (ne na `/var/lib/postgresql/data`), kot zahteva slika `postgres` od različice 18 naprej. Podatki so v podmapi `<PG_MAJOR>/docker`, kar omogoča nadgradnjo s `pg_upgrade --link`.
- **Servis `db` ima healthcheck** (`pg_isready`), servis `app` pa se zažene šele, ko je baza pripravljena na povezave.
- **Ob zagonu servisa `app` se najprej izvede `bin/rails db:prepare`**, ki ustvari bazo (če še ne obstaja) in izvede čakajoče migracije. Po `docker compose down -v` zato baze ni treba ustvarjati ročno.
## Nov projekt iz predloge
### 1. Ustvari repozitorij iz predloge
Repozitorij `rails-docker-starter` je v Gitei označen kot predloga (*Make repository a template*).
1. V Gitei izberi **+ → New Repository**.
2. V polju **Template** izberi `rails-docker-starter`.
3. Obkljukaj **Git Content**. Ostale možnosti (webhooki, oznake ...) niso potrebne.
4. Vpiši ime novega repozitorija (npr. `moj-projekt`) in klikni **Create Repository**.
Nov repozitorij dobi datoteke predloge brez njene zgodovine. Gitea v datotekah, naštetih v `.gitea/template`, zamenja spremenljivke z imenom repozitorija: v `.env.example` tako `${REPO_NAME_SNAKE}_development` postane `moj_projekt_development`.
Nato repozitorij kloniraj:
```bash
git clone <url-novega-repozitorija>
cd moj-projekt
```
Če predlogo namesto tega kloniraš neposredno (`git clone`, nato `rm -rf .git && git init`), Gitea spremenljivk ne zamenja. V `.env` moraš `${REPO_NAME_SNAKE}` zamenjati ročno, sicer Compose spremenljivko razume kot prazno in baza se imenuje `_development`.
### 2. Nastavi okoljske spremenljivke
```bash
cp .env.example .env
```
V `.env` preveri ime baze in vpiši uporabnika in geslo. `APP_DATABASE_HOST` naj ostane `db` (ime servisa v Compose).
### 3. Zgradi sliko in namesti Rails
```bash
docker compose build
docker compose run --rm app gem install rails
```
### 4. Ustvari Rails aplikacijo
```bash
docker compose run --rm app rails new . --database=postgresql --skip-docker
```
- `--skip-docker` prepreči, da bi Rails ustvaril svoj (produkcijski) `Dockerfile` in prepisal obstoječega.
- Ko vpraša, ali naj prepiše `.gitignore`, odgovori `n`. Predloga že vsebuje vse vrstice, ki jih doda Rails.
- Ko vpraša, ali naj prepiše `README.md`, odgovori `n`, sicer izgubiš ta navodila. Navodila za aplikacijo lahko pozneje dopišeš sem.
### 5. Poveži Rails z bazo
V `config/database.yml` dodaj nastavitve povezave v razdelek `default`, da veljajo za `development` in `test`:
```yaml
default: &default
adapter: postgresql
encoding: unicode
max_connections: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
# Povezava na bazo v kontejnerju db (vrednosti iz .env)
host: <%= ENV["APP_DATABASE_HOST"] %>
port: <%= ENV.fetch("APP_DATABASE_PORT") { 5432 } %>
username: <%= ENV["APP_DATABASE_USERNAME"] %>
password: <%= ENV["APP_DATABASE_PASSWORD"] %>
development:
<<: *default
database: <%= ENV["APP_DATABASE"] %>
test:
<<: *default
database: <%= ENV["APP_DATABASE"] %>_test
```
Brez teh nastavitev se Rails poskuša povezati prek lokalnega socketa in javi `PG::ConnectionBad: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed`.
### 6. Dovoli web-console iz omrežja Docker
Brskalnik dostopa do aplikacije prek omrežja Docker (npr. `172.27.0.1`), web-console pa privzeto dovoli le `127.0.0.1`. V dnevniku se zato izpiše `Cannot render console from 172.27.0.1!`, konzola na strani z napako pa se ne prikaže. V `config/environments/development.rb` dodaj:
```ruby
# Dovoli web-console za zahteve iz omrežij Docker (privzeti bazeni naslovov).
config.web_console.allowed_ips = %w[172.16.0.0/12 192.168.0.0/16]
```
### 7. Zaženi aplikacijo
```bash
docker compose up
```
Ob zagonu se samodejno izvede `bin/rails db:prepare`, ki ustvari bazo. Aplikacija je dosegljiva na <http://localhost:3000>.
### 8. Odkomentiraj namestitev gemov v Dockerfile
Ko obstajata `Gemfile` in `Gemfile.lock`, v `Dockerfile` odkomentiraj:
```dockerfile
COPY --chown=app:app Gemfile Gemfile.lock ./
RUN bundle install
```
Ob gradnji slike se tako namestijo gemi, Docker pa to plast shrani v predpomnilnik.
## Pogosti ukazi
| Opravilo | Ukaz |
|---|---|
| Zagon | `docker compose up` |
| Zagon v ozadju | `docker compose up -d` |
| Ustavitev | `docker compose down` |
| Rails konzola | `docker compose run --rm app bin/rails console` |
| Migracije | `docker compose run --rm app bin/rails db:migrate` |
| Generator | `docker compose run --rm app bin/rails generate model Company name:string` |
| Testi | `docker compose run --rm app bin/rails test` |
| Lupina v kontejnerju | `docker compose run --rm app bash` |
| Konzola PostgreSQL | `docker compose exec db psql -U <uporabnik> <baza>` |
| Dnevnik | `docker compose logs -f app` |
Če aplikacija že teče (`docker compose up`), lahko namesto `run --rm` uporabiš `exec`, ki ukaz izvede v obstoječem kontejnerju.
## Dodajanje gemov
```bash
docker compose run --rm app bundle add <ime_gema>
```
Po ročni spremembi `Gemfile`:
```bash
docker compose run --rm app bundle install
```
Volume `bundle` prekrije geme, nameščene ob gradnji slike. Po spremembi `Gemfile` zato vedno poženi `bundle install` v kontejnerju; samo ponovna gradnja slike ni dovolj.
## Če potrebuješ Node.js
Privzeti Rails 8 projekt Node.js ne potrebuje: JavaScript se naloži prek **importmap**, sredstva obdela **Propshaft**. Tudi Tailwind (`--css=tailwind`) in Dart Sass (`dartsass-rails`) uporabljata samostojni izvršljivi datoteki brez npm.
Node.js je potreben, če pri `rails new` izbereš:
- `--javascript=esbuild`, `rollup` ali `webpack` (jsbundling-rails),
- `--css=bootstrap`, `bulma`, `postcss` ali `sass` (cssbundling-rails),
ali če uporabljaš knjižnice, ki jih je treba prevesti (npr. React z JSX).
Paket `nodejs` iz Debiana za to ne zadošča, ker ne vsebuje npm in je pogosto zastarel. Node.js raje prekopiraj iz uradne slike (pred vrstico `USER app`):
```dockerfile
COPY --from=node:24-slim /usr/local/bin/node /usr/local/bin/
COPY --from=node:24-slim /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
```
Različico (`24`) izberi glede na aktualni LTS. Če potrebuješ yarn, dodaj še `RUN npm install -g yarn` (prav tako pred `USER app`).
## Odpravljanje težav
**`Permission denied` pri `gem install` ali `bundle install`**
Volume `bundle` je verjetno nastal s starejšo različico slike, ko je bila mapa v lasti uporabnika root. Odstrani ga in zgradi sliko znova:
```bash
docker compose down
docker volume ls # poišči <projekt>_bundle
docker volume rm <projekt>_bundle
docker compose build
```
**`could not translate host name "db"` ali `connection refused`**
Preveri, da je v `.env` nastavljeno `APP_DATABASE_HOST=db` in da teče servis baze (`docker compose ps`, stanje `healthy`). Pri `docker compose run` Compose sicer počaka na healthcheck, pri ročnem zagonu servisa `db` pa PostgreSQL ob prvem zagonu potrebuje nekaj sekund za inicializacijo.
**`PG::ConnectionBad: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed`**
Rails ne uporablja nastavitev iz `.env` in se poskuša povezati lokalno. Preveri `config/database.yml` (glej korak 5) in znova zaženi servis `app` (`docker compose restart app`).
**Kontejner `db` se ustavi z `Error: in 18+, these Docker images are configured to store database data in a format which is compatible with "pg_ctlcluster"`**
Volume `pgdata` je priklopljen na `/var/lib/postgresql/data`, slika `postgres` od različice 18 naprej pa pričakuje `/var/lib/postgresql`. Popravi priklop v `docker-compose.yml`. Če so v volumnu podatki starejše glavne različice, jih nadgradi s `pg_upgrade` ali izvozi in uvozi, sicer volume izbriši (`docker volume rm <projekt>_pgdata`).
**`A server is already running`**
Ostala je datoteka s PID-om strežnika:
```bash
rm tmp/pids/server.pid
```
**Ponastavitev baze od začetka**
```bash
docker compose down -v # izbriše VSE volumne, tudi podatke v bazi
```
## Opombe
- Predloga je namenjena **razvoju**. Za produkcijo uporabi Dockerfile, ki ga ustvari `rails new` (brez `--skip-docker`), ali Kamal.
- `.env` nikoli ne shranjuj v Git. V repozitorij gre samo `.env.example`.