Skip to main content

Ruby on Rails mit Docker für die lokale Entwicklungsumgebung

Artikel von

Mikhail Tereschenko

blog feature ruby security

2. November 2022

0 Min. Lesezeit

Hallo, Ruby-Entwicklerinnen und -Entwickler! Wenn Sie nach einer effektiven Möglichkeit suchen, Ruby on Rails mit Docker in Ihrer lokalen Entwicklungsumgebung einzurichten, ist dieser Beitrag genau richtig für Sie. Er knüpft an unseren vorherigen Artikel darüber an, wie Sie Ruby unter macOS für die lokale Entwicklung installieren.

Ruby-Entwicklerinnen und -Entwickler müssen bei der Entwicklung eines Ruby-on-Rails-Projekts häufig eine Datenbank sowie weitere Voraussetzungen für die Entwicklungsumgebung berücksichtigen. Es kann jedoch eine Herausforderung sein, sicherzustellen, dass all diese Komponenten auf Ihrem Hostbetriebssystem installiert sind und genau die erforderlichen Versionen verwenden.

Jedes Mal, wenn ich ein neues Projekt erstellen wollte, wünschte ich mir einen Button, der dafür eine neue, saubere Umgebung einrichtet. Deshalb habe ich eine Docker-gestützte Umgebung erstellt, die sich mit Docker docker-compose.yml ganz einfach starten und stoppen lässt.

Was ist Docker Compose?

Docker Compose ist ein Infrastructure-as-Code-Tool (IaC), mit dem Sie Anwendungen und Dienste wie Proxys, Datenbanken und Volume-Mounts konfigurieren und miteinander verbinden können, um sie wiederverwendbar bereitzustellen. Es ist im Docker-Softwarepaket enthalten und wird mit YAML definiert.

Diese Docker-Compose-Konfiguration für eine lokale Entwicklungsumgebung eignet sich für verschiedene Projekttypen und ist nicht nur auf Ruby on Rails beschränkt. Sie kann auch für Nebenprojekte oder clientseitige Frontend-Projekte verwendet werden. Die wichtigsten Gründe, warum ich Docker-Umgebungen Änderungen am eigenen Hostbetriebssystem vorziehe, sind:

  • einfach zu konfigurieren

  • Logik einfach von der Umgebung zu trennen

  • dieselbe Umgebung problemlos für verschiedene Projekte zu verwenden

  • die Umgebung problemlos mit allen zu teilen, die Hilfe benötigen, ohne vertraulichen Code weiterzugeben

  • der Hostcomputer bleibt sauber

Eine Ruby-on-Rails-Docker-Umgebung

Um eine Ruby-on-Rails-Docker-Umgebung für die lokale Entwicklung einzurichten, benötigen Sie Folgendes:

  • Ein Unix-ähnliches Betriebssystem (ich verwende macOS, Sie können aber Ubuntu oder eine andere Linux-Distribution nutzen. Windows sollte ebenfalls funktionieren)

  • Git

  • make (Version 3.81 oder höher)

  • Docker (Version 19.03.12 oder höher)

  • docker-compose (Version 1.26.0 oder höher)

  • Ihr bevorzugter Texteditor (in meinem Fall Visual Studio Code)

Sobald Sie all das haben, können Sie loslegen. Falls Sie nicht lesen möchten: Hier finden Sie das Open-Source-Repository mit allen erforderlichen Anweisungen und dem vollständigen Quellcode zu den Codeausschnitten, die wir uns ansehen. Da Sie bereits hier sind, folgen Sie einfach dieser Schritt-für-Schritt-Anleitung.

Zunächst erstelle ich der Einfachheit halber ein Ruby-on-Rails-Projekt mit dem Namen foo_bar_project.

1) Rufen Sie die Docker-Compose-Umgebungskonfiguration ab und entfernen Sie das Git-Repository. Wenn Sie alles in einem Git-Quellcode-Repository nachverfolgen möchten, können Sie später Ihr eigenes erstellen.

git clone git@github.com:mtereschenko/simple_ror_environment.git && cd simple_ror_environment && rm -rf .git

Die nächsten Schritte sollten im Verzeichnis ./environment ausgeführt werden. Stellen Sie also sicher, dass Sie die Befehle dort ausführen:

cd ./environment

2) Jetzt müssen Sie dieser Umgebungskonfiguration einen Projektnamen zuweisen. Standardmäßig lautet er test_project. Für unseren hier beschriebenen Code müssen wir ihn wie zuvor erläutert in foo_bar_project ändern. Nehmen Sie dazu die Änderungen in der Datei ./environment/.env.example vor und passen Sie den Wert der Variablen PROJECT_NAME an. Verwenden Sie für Variablenwerte unbedingt Snake Case in Kleinbuchstaben:

PROJECT_NAME=foo_bar_project

3) Als Nächstes können Sie Ihr Projekt mit dem folgenden Befehl erstellen:

make init

Wie Sie sehen, gibt es jetzt einen neuen Ordner namens application, der Ruby-on-Rails-Code enthält.

4) Jetzt müssen Sie Ihre lokale Entwicklungsumgebung mit Docker Compose erstellen. Dieser Schritt ist nach jeder Änderung an der Dockerfile oder Gemfile Ihrer Anwendung erforderlich.

make build

5) Damit können Sie nun die lokale Ruby-Entwicklungsumgebung wie folgt starten:

make start

Beachten Sie, dass dadurch noch keine Ruby-on-Rails-Anwendung gestartet wird. Dazu müssen wir eine Shell in der Docker-Umgebung öffnen und sie anweisen, den Prozess rails zu starten. So geht's:

make shell
rails s -p 3000 –binding=0.0.0.0

Ruby on Rails mit Docker für die lokale Entwicklungsumgebung. Wenn Sie schließlich Ihren Browser öffnen und diese URL aufrufen: http://foo_bar_project.localhost/, sollten Sie das gerade erstellte Ruby-on-Rails-Projekt sehen.

Was steht uns jetzt also zur Verfügung?

  • Wir haben ein Projekt mit einem Umgebungsverzeichnis, das alles enthält, was wir zum Ausführen des Projekts benötigen, und einem Anwendungsverzeichnis mit unserem Anwendungscode. Dieses Verzeichnis ist in den Docker-Container eingebunden, sodass Sie über das Ruby-Docker-Image uneingeschränkten Zugriff darauf haben.

  • Wir können schnell auf die Ruby-Shell zugreifen und müssen daher keine Ruby-Umgebung wie Rails, Rake, Bundler oder eine andere Ruby-Toolchain lokal installieren.

  • Befehle können nach dem Befehl make shell ausgeführt werden.

  • Diese Docker-Compose-Konfiguration des Projekts umfasst zwei vorkonfigurierte Stages. Wenn Sie Ihre Ruby-Anwendung also einmal bereitstellen möchten, können Sie eine eigene Stage erstellen. So einfach ist das!

Wenn Sie wissen möchten, wie die oben beschriebene Docker-Umgebung für Ruby on Rails funktioniert, sehen wir uns die einzelnen Komponenten an: das Ruby-Docker-Image, die Makefile und die Docker-Compose-Definition, die alles zusammenbringt.

Ein Ruby-Docker-Image

Zunächst ist das Ruby-Docker-Image ein wesentlicher Bestandteil. Es ist wichtig, es richtig zu erstellen, damit der Cache der Docker-Image-Layer effektiv wiederverwendet werden kann und auch andere Anforderungen einer lokalen Entwicklungsumgebung erfüllt werden.

Hier sehen Sie die Dockerfile für das Ruby-Docker-Image:

FROM ruby:2.7.6-alpine3.16 as base_image
RUN apk add --no-cache git \
    build-base \
    libpq-dev \
    tzdata

FROM base_image as development
COPY ./artifacts/rails/Gemfile /tmp/Gemfile
COPY ./artifacts/rails/Gemfile.lock /tmp/Gemfile.lock
COPY ./containers/ruby/runners/runner.development.sh /rdebug_ide/runner.sh
RUN cd /tmp && \
  gem install ruby-debug-ide && \
  gem install debase && \
  bundle install && \
  chmod +x /rdebug_ide/runner.sh && \
  apk add --no-cache git \
  nodejs \
  yarn
WORKDIR /app

ENTRYPOINT ["tail", "-f", "/dev/null"]

FROM base_image as init
COPY ./containers/ruby/initializers/runner.init.sh /tmp/runner.sh
COPY ./containers/ruby/initializers/database.yml /tmp/database.yml
COPY ./containers/ruby/initializers/.gitignore /tmp/.gitignore
COPY ./containers/ruby/initializers/development.rb /tmp/development.rb
RUN chmod +x /tmp/runner.sh

WORKDIR /app

ENTRYPOINT ["/tmp/runner.sh"]

Wie Sie sehen, werden einige Annahmen zu den zusätzlich erforderlichen Konfigurationen getroffen, damit eine funktionsfähige Ruby-on-Rails-Anwendungsumgebung bereitsteht. Dazu gehören:

  • die Datei database.yml, die mit den Verbindungsdetails für die Ruby-on-Rails-Datenbank befüllt wird

  • development.rb für die Ruby-on-Rails-Laufzeitkonfiguration der Entwicklungsumgebung

Dazu wird das Skript runners.sh im Verzeichnis ./environment/containers/ruby/initializers verwendet:

#!/bin/ash

cd /app

gem install rails

rails new . -d=postgresql --skip-git

yes | cp -rf /tmp/database.yml /app/config/database.yml
yes | cp -rf /tmp/development.rb /app/config/environments/development.rb
cp /tmp/.gitignore /app/.gitignore

Diese Dockerfile nutzt außerdem Docker-Multistage-Builds, sodass bestimmte Teile bei Bedarf effektiv in verschiedenen Umgebungen wiederverwendet werden können, wenn Sie dieselbe Konfiguration für unterschiedliche Workflows wie Tests oder Staging einsetzen möchten.

Eine Rails-Docker-Compose-Konfiguration

Die Docker-Compose-Datei unter `./environment/docker-compose.development.yml` verbindet alle Dienste zu einer funktionsfähigen Ruby-on-Rails-Anwendung in Docker:

  • Ein nginx-HTTP-Server

  • Eine Ruby-Anwendung

  • Ein PostgreSQL-Datenbankserver

Hier sehen Sie die von dieser Konfiguration verwendete Rails-Docker-Compose-Datei:

version: '3.7'
services:
  nginx:
    image: "${PROJECT_NAME}/nginx:development"
    container_name: ${PROJECT_NAME}-nginx
    build: 
      context: ./
      dockerfile: ./containers/nginx/Dockerfile
    depends_on:
      - ruby
    tty: true
    ports:
      - 80:80
    volumes:
      - ./`artifacts`/nginx/:/`var`/log/nginx:cached
   ruby:
    image: "${PROJECT_NAME}/ruby:development"
    container_name: ${PROJECT_NAME}-ruby
    depends_on:
      - postgres
    build:
      context: ./
      dockerfile: ./containers/ruby/Dockerfile
      target: development
    ports:
      - 13030:13030
    volumes:
      - ${APP_PATH}:/app:cached
    environment:
      DB_NAME: ${DB_NAME}
      PROJECT_NAME: ${PROJECT_NAME}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_PORT: ${DB_PORT}
      PUMA_WORKERS: 0
      RAILS_MAX_THREADS: 1
     postgres:
    image: "${PROJECT_NAME}/postgres:development"
    container_name: ${PROJECT_NAME}-postgres
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    build:
      context: ./
      dockerfile: ./containers/postgres/Dockerfile
    ports:
      - ${DB_PORT}:5432
    volumes:
      - postgres_volume:/var/lib/postgresql/data

volumes:
  postgres_volume:

Makefile für Docker

Um Entwicklerinnen und Entwicklern schließlich eine leicht zugängliche Schnittstelle für die Interaktion mit dieser lokalen Ruby-Entwicklungsumgebung bereitzustellen, wird ein Makefile verwendet:

.PHONY: help
# Make stuff

-include .env

export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1

.DEFAULT_GOAL := help

ARTIFACTS_DIRECTORY := "./artifacts"

CURRENT_PATH :=${abspath .}

SHELL_CONTAINER_NAME := $(if $(c),$(c),ruby)
BUILD_TARGET := $(if $(t),$(t),development)

help: ## Help.
@grep -E '^[a-zA-Z-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "[32m%-27s[0m %s\n", $$1, $$2}'

init: ## Project installation.
@rm -f ./.env
@cp .env.example .env
@make init_app_directory
@make create_postgress_volume
@docker-compose -f docker-compose.init.yml build
@docker-compose -f docker-compose.init.yml up

build: ## Build images.
@make create_project_artifacts
@cp ${APP_PATH}/Gemfile "${ARTIFACTS_DIRECTORY}/rails/Gemfile"
@cp ${APP_PATH}/Gemfile.lock "${ARTIFACTS_DIRECTORY}/rails/Gemfile.lock"
@docker-compose -f docker-compose.$(BUILD_TARGET).yml build

shell: ## Internal image bash command line.
@if [[ -z `docker ps | grep ${SHELL_CONTAINER_NAME}` ]]; then \
echo "${SHELL_CONTAINER_NAME} is NOT running (make start)."; \
else \
docker-compose -f docker-compose.$(BUILD_TARGET).yml exec $(SHELL_CONTAINER_NAME) /bin/ash; \
fi

start: ## Start previously builded application images.
@make create_project_artifacts
@make start_postgres
@make start_ruby
@make start_nginx

run: ## Run ruby debugger session.
@docker-compose -f docker-compose.$(BUILD_TARGET).yml exec ruby /bin/ash /rdebug_ide/runner.sh

start_ruby: ## Start ruby image.
@if [[ -z `docker ps | grep ruby` ]]; then \
docker-compose -f docker-compose.$(BUILD_TARGET).yml up -d ruby; \
else \
echo "Ruby is running."; \
fi

start_postgres: ## Start postgres image.
@if [[ -z `docker ps | grep postgres` ]]; then \
docker-compose -f docker-compose.$(BUILD_TARGET).yml up -d postgres; \
else \
echo "Postgres is running."; \
fi

start_nginx: ## Start nginx image.
@if [[ -z `docker ps | grep nginx` ]]; then \
docker-compose -f docker-compose.$(BUILD_TARGET).yml up -d nginx; \
else \
echo "Nginx is running."; \
fi

stop: ## Stop all images.
@docker-compose -f docker-compose.$(BUILD_TARGET).yml stop

create_project_artifacts:
mkdir -p ./artifacts/rails
mkdir -p ./artifacts/db

init_app_directory:
@mkdir -p ${APP_PATH}

create_postgress_volume:
@sed -i '' -r  "s/postgres_volume:/${PROJECT_NAME}_db_volume:/g" docker-compose.development.yml

Ruby-Anwendungen effizient ausführen

Hoffentlich haben Sie nun eine neue Fähigkeit erworben: Ruby-Anwendungen wie Ruby on Rails mithilfe einer Docker-Containerkonfiguration in Ihrer lokalen Umgebung auszuführen. Eine weitere Möglichkeit, Ruby-Anwendungen lokal auszuführen, sind Ruby-Virtual-Environments. Diese haben wir bereits in unserem Beitrag zur Installation von Ruby unter macOS behandelt.

Nachdem Sie Ihre Ruby-Entwicklungsumgebung eingerichtet haben, möchten Sie vielleicht auch erfahren, wie Sie Ihre Ruby-Anwendungen schützen. Weitere Informationen zur Ruby-Sicherheit finden Sie in den folgenden Artikeln:

Container-Sicherheit mit Fokus auf Entwickler

Snyk findet und behebt automatisch Schwachstellen in Container-Images und Kubernetes-Workloads.

Weiterlesen

feature customer snowflake
Article

Sicherheit von Anfang an: Die Snyk Studio-Integration für Snowflake Cortex Code

Snyk Studio ist in Snowflake Cortex Code integriert und scannt KI-generierten Code, Abhängigkeiten und Container während der Entwicklung auf Schwachstellen.

blog feature pypi spoof
Article

Die gesamte Snyk AI Security Platform – kostenlos für Open-Source-Maintainer

Open-Source-Maintainer werden von echten Schwachstellenmeldungen überflutet und brauchen Unterstützung dabei, diese zu priorisieren, zu beheben und schneller Korrekturen bereitzustellen. Das Secure Developer Program von Snyk bietet qualifizierten Projekten kostenlosen Zugang zur Snyk AI Security Platform.

feature insights announcement
Blog

Node-gyp-Supply-Chain-Kompromittierung: Ein sich selbst verbreitender npm-Wurm, der sich in binding.gyp versteckt

Ein neuer npm-Wurm missbraucht binding.gyp, um node-gyp während der Installation auszuführen. So können schädliche Pakete Code ohne Lifecycle-Skripte ausführen. Der Wurm stiehlt Zugangsdaten, setzt sich in GitHub fest und verbreitet sich selbstständig über Maintainer.