Resources

This episode is sponsored by Honeybadger

Download Source Code

Summary

# Terminal
rails new testapp --javascript esbuild --css bootstrap -m main.rb

# generate alias in .zshrc
alias generate='f(){ rails new "$@" -m ~/Rails/generator/main.rb;  unset -f f; }; f'

# .railsrc
--skip-spring
--javascript esbuild
--css bootstrap

# main.rb
def source_paths
  [__dir__]
end

def ask_with_default(prompt, default)
  value = ask("#{prompt} [#{default}]")
  value.blank? ? default : value
end

def app_name_dasherized
  app_name.gsub('_', '-')
end

def registry
  @registry ||= ask_with_default("What is your registry username?", "kobaltz")
end

def domain
  @domain ||= ask_with_default("What is the domain name of your server?", "railsapp.dev")
end

registry
domain

rails_command "db:system:change --to=postgresql"
remove_file "config/database.yml"
template "config/database.yml.erb", "config/database.yml"

generate(:controller, 'welcome', 'index')
route "root to: 'welcome#index'"

template './docker_files/.dockerignore.erb', '.dockerignore'
template './docker_files/docker-compose.yml.erb', 'docker-compose.yml'
template './docker_files/Dockerfile.dev.erb', 'Dockerfile.dev'
template './docker_files/docker-compose-prod.yml.erb', 'docker-compose-prod.yml'
template './docker_files/Dockerfile.prod.erb', 'Dockerfile.prod'

template './bin/deploy.erb', 'bin/deploy'
template './bin/entrypoint.sh.erb', 'bin/entrypoint.sh'

after_bundle do
  run 'bundle lock --add-platform aarch64-linux'
  run 'bundle lock --add-platform x86_64-darwin-19'
  run 'bundle lock --add-platform x86_64-linux'

  remove_file 'bin/dev'
  template './bin/dev.erb', 'bin/dev'

  run 'chmod +x ./bin/dev'
  run 'chmod +x ./bin/deploy'
  run 'chmod +x ./bin/entrypoint.sh'

  remove_file 'Procfile.dev'

  run 'docker compose run app bundle install'
  run 'docker compose run app yarn install'
  run 'docker compose run app bin/rails bin/rails db:create db:migrate db:seed'
  run 'docker compose down'

  git :init
  git add: '.'
  git commit: %( -m 'base' )
end

# bin/deploy.erb
#!/bin/bash

docker login

docker buildx create \
  --name amd64_builder \
  --node linux_amd64_builder \
  --platform linux/amd64 \
  ssh://kobaltz@192.168.1.89

docker buildx build \
  --file Dockerfile.prod \
  --builder amd64_builder \
  --platform linux/amd64 \
  --tag <%= registry %>/<%= app_name.underscore %>_app:latest \
  --push .

# bin/dev.erb
#!/usr/bin/env bash

docker compose up

# bin/entrypoint.sh.erb
#!/bin/bash

set -e

if [ -f /app/tmp/pids/server.pid ]; then
  rm /app/tmp/pids/server.pid
fi

bin/rails db:migrate 2>/dev/null || DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bin/rails db:setup

exec bundle exec "$@"

# config/database.yml.erb
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  url:  <%%= ENV.fetch("DATABASE_URL") { 'localhost' } %>

development:
  <<: *default
  database: <%= app_name_dasherized %>_development

test:
  <<: *default
  database: <%= app_name_dasherized %>_test

production:
  <<: *default
  database: <%= app_name_dasherized %>_production

# docker_files/.dockerignore.erb
.git*
log/*
tmp/*
tmp/pid/server.pid
Dockerfile
README.rdoc

# docker_files/docker-compose.yml.erb
version: '3.8'
x-base: &base
  build:
    context: .
    dockerfile: ./Dockerfile.dev
    args:
      RUBY_VERSION: '3.1.2'
  stdin_open: true
  tty: true
  volumes:
    - .:/app:cached
  environment:
    - DATABASE_URL=postgres://postgres:postgres@postgres:5432
  depends_on:
    - redis
    - postgres

services:
  app:
    <<: *base
    command: bin/rails server -p 3000 -b 0.0.0.0
    ports:
      - '3000:3000'

  css:
    <<: *base
    command: yarn build:css --watch

  js:
    <<: *base
    command: yarn build --watch

  # sidekiq:
  #   <<: *base
  #   command: bundle exec sidekiq -C config/sidekiq.yml

  redis:
    image: redis:latest
    restart: always

  postgres:
    image: postgres:14-alpine
    volumes:
      - postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=postgres
    ports:
      - 5432

volumes:
  postgres:

# docker_files/docker-compose-prod.yml.erb
version: '3.8'

networks:
  traefik-public:
    external: true
  <%= app_name.underscore %>_network:

volumes:
  postgres:
  storage:

services:
  app:
    image: <%= registry %>/<%= app_name.underscore %>_app:latest
    restart: always
    command: bin/rails server -p 3000 -b 0.0.0.0
    environment:
      RAILS_SERVE_STATIC_FILES: "enabled"
      RAILS_LOG_TO_STDOUT: "enabled"
      DATABASE_URL: postgres://postgres:postgres@postgres:5432
    volumes:
      - storage:/app/storage
    ports:
      - 3000
    depends_on:
      - postgres
      - redis
    networks:
      - traefik-public
      - <%= app_name.underscore %>_network
    deploy:
      replicas: 2
      labels:
        - traefik.enable=true
        - traefik.http.services.<%= app_name.underscore %>.loadbalancer.server.port=3000
        - traefik.http.routers.<%= app_name.underscore %>.rule=Host(`<%= app_name.underscore %>.<%= domain %>`)
        - traefik.http.routers.<%= app_name.underscore %>.entrypoints=web
        - traefik.constraint-label=traefik-public
        - traefik.docker.network=traefik-public

  redis:
    image: redis:latest
    restart: always
    networks:
      - <%= app_name.underscore %>_network

  postgres:
    image: postgres:14-alpine
    volumes:
      - postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=postgres
    ports:
      - 5432
    networks:
      - <%= app_name.underscore %>_network

# docker_files/Dockerfile.dev.erb
ARG RUBY_VERSION
FROM ruby:$RUBY_VERSION

RUN apt update -qq && apt upgrade -y
RUN apt-get install -y build-essential
RUN apt install -y postgresql-client
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt update -qq
RUN apt install -y nodejs yarn
RUN gem update --system
RUN gem install bundle

WORKDIR /app
RUN echo 'gem: --no-rdoc --no-ri >> "$HOME/.gemrc"'

COPY Gemfile Gemfile.lock ./
COPY package.json yarn.lock ./

RUN bundle install -j $(nproc)
RUN yarn install

# docker_files/Dockerfile.prod.erb
FROM ruby:3.1.2
ENV RAILS_ENV production

RUN apt update -qq && apt upgrade -y
RUN apt-get install -y build-essential
RUN apt install -y postgresql-client
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt update -qq
RUN apt install -y nodejs yarn
RUN gem update --system
RUN gem install bundle

WORKDIR /app
RUN echo 'gem: --no-rdoc --no-ri >> "$HOME/.gemrc"'

COPY Gemfile Gemfile.lock ./
COPY package.json yarn.lock ./

RUN bundle config set --local without 'development test'
RUN bundle install -j $(nproc)
RUN yarn install

COPY . /app

RUN RAILS_GROUPS=assets bin/rails assets:precompile
ENTRYPOINT [ "/app/bin/entrypoint.sh" ]
CMD ["bin/rails", "server", "-b", "0.0.0.0", "-p", "3000"]
EXPOSE 3000