# 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-` 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:`) 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 `/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 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 . ### 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 ` | | 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 ``` 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 _bundle docker volume rm _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 _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`.