Skip to content

Commit

Permalink
ActionCable configuration for development and production (using AnyCa…
Browse files Browse the repository at this point in the history
…ble)
  • Loading branch information
RISCfuture committed Sep 14, 2024
1 parent b37c39b commit 2f27254
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 77 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up flyctl
uses: superfly/flyctl-actions/setup-flyctl@master
- name: Deploy to Fly.io
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
spec/examples.txt
bin/dist
46 changes: 24 additions & 22 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,62 +1,64 @@
# syntax = docker/dockerfile:1

# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t my-app .
# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.3.5
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
FROM ruby:$RUBY_VERSION-slim as base

LABEL fly_launch_runtime="rails"

# Rails app lives here
WORKDIR /rails

# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
ENV BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development:test"
BUNDLE_WITHOUT="development:test" \
RAILS_ENV="production"

# Update gems and bundler
RUN gem update --system --no-document && \
gem install -N bundler


# Throw-away build stage to reduce size of final image
FROM base AS build
FROM base as build

# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev pkg-config libvips && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
apt-get install --no-install-recommends -y build-essential libpq-dev libvips

# Install application gems
COPY Gemfile Gemfile.lock ./
COPY --link Gemfile Gemfile.lock ./
RUN bundle install && \
bundle exec bootsnap precompile --gemfile && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git

# Copy application code
COPY . .

COPY --link . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/


# Final stage for app image
FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl imagemagick libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
chown -R 1000:1000 db log storage tmp
USER 1000:1000

# Entrypoint prepares the database.
# Entrypoint sets up the container.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
Expand Down
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gem "bootsnap"
gem "good_job"
gem "rack-cors"
gem "rails"
gem "puma"

Check failure on line 12 in Gemfile

View workflow job for this annotation

GitHub Actions / Linters

Bundler/OrderedGems: Gems should be sorted in an alphabetical order within their section of the Gemfile. Gem `puma` should appear before `rails`.

# MODELS
gem "active_storage_validations"
Expand All @@ -27,12 +28,14 @@ gem "redis"
gem "devise"
gem "devise-jwt"

# ACTION CABLE
gem "anycable-rails"

# ERRORS
gem "bugsnag"

group :development do
gem "listen"
gem "puma"

# LINTING
gem "brakeman", require: false
Expand Down
33 changes: 32 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,23 @@ GEM
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
anycable (1.5.1)
anycable-core (= 1.5.1)
grpc (~> 1.53)
anycable-core (1.5.1)
anyway_config (~> 2.2)
google-protobuf (~> 3.25)
anycable-rails (1.5.2)
anycable (~> 1.5.0)
anycable-rails-core (= 1.5.2)
anycable-rails-core (1.5.2)
actioncable (>= 6.0)
anycable-core (~> 1.5.0)
globalid
anyway_config (2.6.4)
ruby-next-core (~> 1.0)
aws-eventstream (1.3.0)
aws-partitions (1.971.0)
aws-partitions (1.972.0)
aws-sdk-core (3.203.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
Expand Down Expand Up @@ -167,6 +182,20 @@ GEM
fugit (>= 1.11.0)
railties (>= 6.1.0)
thor (>= 1.0.0)
google-protobuf (3.25.4-aarch64-linux)
google-protobuf (3.25.4-arm64-darwin)
google-protobuf (3.25.4-x86_64-linux)
googleapis-common-protos-types (1.15.0)
google-protobuf (>= 3.18, < 5.a)
grpc (1.66.0-aarch64-linux)
google-protobuf (>= 3.25, < 5.0)
googleapis-common-protos-types (~> 1.0)
grpc (1.66.0-arm64-darwin)
google-protobuf (>= 3.25, < 5.0)
googleapis-common-protos-types (~> 1.0)
grpc (1.66.0-x86_64-linux)
google-protobuf (>= 3.25, < 5.0)
googleapis-common-protos-types (~> 1.0)
hashdiff (1.1.1)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
Expand Down Expand Up @@ -310,6 +339,7 @@ GEM
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.1)
ruby-next-core (1.0.3)
ruby-vips (2.2.2)
ffi (~> 1.12)
logger
Expand Down Expand Up @@ -350,6 +380,7 @@ PLATFORMS

DEPENDENCIES
active_storage_validations
anycable-rails
aws-sdk-s3
binding_of_caller
boolean
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ An example Foreman script that accomplishes all of this:
backend: cd Backend && rvm 3.3.5@greenie exec rails server
frontend: cd Frontend && yarn serve
workers: cd Backend && rvm 3.3.5@greenie exec bundle exec good_job start
cable: cd Backend && rvm 3.3.5@greenie exec ./bin/cable
anycable: cd Backend && rvm 3.3.5@greenie exec anycable
ws: cd Backend && rvm 3.3.5@greenie execcbin/anycable-go --port=8080
mail: mailcatcher -f
```

Expand All @@ -77,12 +78,20 @@ E2E test application:
backend: cd Backend && rvm 3.3.5@greenie exec rails server -e cypress -b localhost
frontend: cd Frontend && yarn run test:e2e
workers: cd Backend && RAILS_ENV=cypress rvm 3.3.5@greenie exec bundle exec good_job start
cable: cd Backend && rvm 3.3.5@greenie exec ./bin/cable -e cypress
anycable: cd Backend && RAILS_ENV=cypress rvm 3.3.5@greenie exec anycable
ws: cd Backend && rvm 3.3.5@greenie execcbin/anycable-go --port=8080
```

#### Deployment

The application is deployed using Capistrano by running `cap production deploy`.
The application is deployed on Fly.io automatically after CI completes, with a
GitHub Action. The `fly.toml` file contains the architecture for the production
environment: an app server, a GoodJob worker server, a Redis cluster, and a
PostgreSQL database cluster.

The Rails processes run on a separate Fly.io cluster from the AnyCable
processes. An nginx cluster reverse-proxies requests for `/cable` to the
AnyCable process, and all other requests to the Rails process.

## Architecture

Expand Down
22 changes: 22 additions & 0 deletions bin/anycable-go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

cd $(dirname $0)/..

# It's recommended to use the exact version of AnyCable here
version="latest"

if [ ! -f ./bin/dist/anycable-go ]; then
echo "AnyCable server is not installed, downloading..."
./bin/rails g anycable:download --version=$version --bin-path=./bin/dist
fi

curVersion=$(./bin/dist/anycable-go -v)

if [[ "$version" != "latest" ]]; then
if [[ "$curVersion" != "$version"* ]]; then
echo "AnyCable server version is not $version, downloading a new one..."
./bin/rails g anycable:download --version=$version --bin-path=./bin/dist
fi
fi

./bin/dist/anycable-go $@
2 changes: 1 addition & 1 deletion bin/cable
Original file line number Diff line number Diff line change
@@ -1 +1 @@
bundle exec puma -p 28080 cable/config.ru --pidfile tmp/pids/cable.pid $*
bundle exec puma -p 8080 cable/config.ru --pidfile tmp/pids/cable.pid $*
42 changes: 42 additions & 0 deletions config/anycable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# This file contains per-environment settings for AnyCable.
#
# Since AnyCable config is based on anyway_config (https://github.com/palkan/anyway_config), all AnyCable settings
# can be set or overridden through the corresponding environment variables.
# E.g., `rpc_host` is overridden by ANYCABLE_RPC_HOST, `debug` by ANYCABLE_DEBUG etc.
#
# Note that AnyCable recognizes REDIS_URL env variable for Redis pub/sub adapter. If you want to
# use another Redis instance for AnyCable, provide ANYCABLE_REDIS_URL variable.
#
# Read more about AnyCable configuration here: https://docs.anycable.io/ruby/configuration
#
default: &default
# Turn on/off access logs ("Started..." and "Finished...")
access_logs_disabled: false
# Whether to enable gRPC level logging or not
log_grpc: false
# Use Redis to broadcast messages to AnyCable server
broadcast_adapter: redis
# You can use REDIS_URL env var to configure Redis URL.
# Localhost is used by default.
# redis_url: "redis://localhost:6379/1"
# Use the same channel name for WebSocket server, e.g.:
# $ anycable-go --redis_channel="__anycable__"
# redis_channel: "__anycable__"

development:
<<: *default
# WebSocket endpoint of your AnyCable server for clients to connect to
# Make sure you have the `action_cable_meta_tag` in your HTML layout
# to propagate this value to the client app
websocket_url: "ws://localhost:8080/cable"

test:
<<: *default

cypress:
<<: *default
websocket_url: "ws://localhost:8080/cable"

production:
<<: *default
websocket_url: ~
2 changes: 2 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,7 @@ class Application < Rails::Application
config.good_job.enable_cron = true
config.good_job.dashboard_default_locale = :en
config.good_job.queues = "greenie_#{Rails.env}_default"

config.host_authorization = { exclude: ->(request) { request.path.start_with?('/up') } }

Check failure on line 85 in config/application.rb

View workflow job for this annotation

GitHub Actions / Linters

Layout/SpaceInsideHashLiteralBraces: Space inside { detected.

Check failure on line 85 in config/application.rb

View workflow job for this annotation

GitHub Actions / Linters

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

Check failure on line 85 in config/application.rb

View workflow job for this annotation

GitHub Actions / Linters

Layout/SpaceInsideHashLiteralBraces: Space inside } detected.
end
end
8 changes: 3 additions & 5 deletions config/cable.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
development:
adapter: redis
adapter: <%= ENV.fetch("ACTION_CABLE_ADAPTER", "any_cable") %>
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: greenie_development

test:
adapter: test

cypress:
adapter: redis
adapter: <%= ENV.fetch("ACTION_CABLE_ADAPTER", "any_cable") %>
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: greenie_cypress

production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: greenie_production
adapter: any_cable
2 changes: 1 addition & 1 deletion config/credentials.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5/tNC7Bf6oNXk7R5vPKWzcxPB70+LGylGly0746Q2XS/UGDkPEKQrJoZ9az8FDuYu5gWaZ7fbbJjmYIPda3o9dAMkU02XHIjFV1DrlKUxdAPZfeY5tXDkDG1VUaqbtE3LMBjL7zHonE+EiI5zR4qyl2EuCuZBV2Nc8QacLpEu+j7Z4g1nw3dcYetAcOyvBRHGQ0vJbF7JiWgNA55sTFoAvv6gU3BzcDprxp3Iu0oF5eoGi6a4aCjoeZR0j4O+PQam3w7BuWbRdIXJegYB7/RdOGLvH15vCHRU9E7NbVJXhCwdjfR6+JFcnCnL6EWhJ0mTJ+vHgqgep+LDmJGQq/coocLApJtaFvFgjC6VrYkTQi/KMEq4dArfc2Wq93qtj4iK7N6WSpoDWikz4os5ubhb6Qz7ZReqgvz+CoE6IGucNQZuiSooHh9S9omfMx8u/TeaGRNjaPMm0KX/AYApoEx8IQyCqI+287HmECVY8vsjdbsFOUKDSKeQEgzXfIiYWEux8fYW3aLcB+6nf1GoTHGZENdWY6qWMjzn7Ggx853Lfx5VwcNXTbgjBMI+ES+lWx/FsYbNMTNI+w0KwyWpZkbMysKcc9uGEDFcTBCx3QTMUrt4Ugu7pB7f/Eg4pNcwHNPeOLmWYGQg8AieTqtnM5xQZf+VwfnqMwJkbx4b/z4F+Xu0x2tH2uLEAEoYt7jvnP7xe0QgsmCCbMb5uHzvO0pnzfFNvo1f894twnc5X39tPLt5QR0qKYvFP3lHH/mMLe4W2r/zXVzKU+c0ioQOKHrrtPO0kXp89Tf40WEewqzMHdj6OQxPkIvFlGbdjzFLe4/+XSQvAfIysDDcr/JggI9OLpC2G/LZGiAOiL/hWy9sOQj5pQ63SkoOfmkPMoAU+wP/nkjuJaq2AH6wvFrvfCEDNsO4fSmGOxEP0Hveo14maj1xVgbgXuBMWsPTn4PPbvqzm9mbY6cS+Zuzr1rJPpaAsZ/W8vUmX8mQ+a1BhT81SuFRykkS364WYgJ74VvNoFazqtsgybBsi+7JKSFXf4HszwOwPLjDmSJhVTNhbQ06599Daq5r7/GhmkHfao9IdKol8Bi2uBknWRfVJiXgJV6OzVWgjYipHHRK+pDpOPAWzUjaUiMLnR+TFdrxaGdDZUympJhgf8/Z6dmZOABvTJEVfo+4vmhteu9pKV68uoHLb9IA8bAzMYFApr0M/TEbA1UJBWX--hkINGodhgQvrOwJs--PyDtORAxBjM7vN5JY3QBMQ==
4ABRMXeR7evhiI/vNb1FCciVfJseu46+gwoZaURf0YljW2xTsR99YH5hnohTbIvAfE9AXXZ5Lt7DyqVHY87oGbRpizJ5i1KF1Qx9p8ynlxdN6AfRKFdf0qet7EaazYReWvNlU6/2c3DSfeJ5xkw+WuZ/lD2VnQQMWoUC2Tp98x2wfUbaxATlqUQ50gof2FKN7PoUEBqqvx7U0lm3FvRqjnhfpr0xvogdym/oqn9Vp+lSiLIhbHs7su1guhQGNPR2xtNiB7FBQFPWeSNrn0PGHtcI1TTHasQtb76QFR2iKty8tuNyZYgb+VDhsnEBwzEZq5K7lgEDI4N6TmGnYslC89xEBDn/+vg4p+W0KBzwVx8njq69I+Pcgkgzru7dmilrwZSmoNIpDHvICPu/tTfvjB7BPut9dQgdtp3lFsVMrD1xxcUTvEn5ExWclQ8rbFanAYI729+XdOjVlU5WdQyXAKILS6yD3g/rZ6VaZXHIxadEuECMyONMSJJxWfYlC9cmlXpN0nzJ6coXpvUiHj40AMXMYdFm8YC/ltWokAPJ1Ion0GE5uzstV+ciX1GMWP23o1Xg1F5gyQv04hzQnuZ9b9g1LUl4spPBk8sdhOPOgpmw2w2BauZkcL2ECL3lSVYo1f0o/YTyS28WWWd2KPd3ctZsQlbsfZGOATfoghjYyV+3chFngeyCQW9FOXmSx8ISO3pMBXU8KUSdkE2xsB8iIqfDBjvj7KxErMSIEYncdNCKvgUb0C0YLECpWbOMeXYTFpE3UCj9ElefO8k2IuePgKFt0lVKQY9hfEANOrDU2XK21UXpnRhNhKY4earABYfTU1UHrDF1g0Jgl5p+s9K0NNCOlycHXAWqhUGWcHaHyPM75pq7g86RoBwUApWIbc7NhNIAakvHRDRRBqb/5ezYiuw0EBLb60oZcGkdOrNhfs3zLDRbIHgC1cgC5DQSpdTyD6WNGUi5ZTVcjVzaLsjM+kOx/aXS+bgNRIx/K3Hb6qGgpi5p69mWCOgL/0Is7q4v9pcwG+LO6hil2tPhO2pvsUNbwDZiNd8YkzlH61M1+ko/zynHucEfqBhOv/KoGmAEUnBg0+4i48ljzj8dfklKT6lMo3rVbD3nBLDGLTbrLr3D2S79W64Yea8euQPt/AA3Y2jC0W1XF+8MYUKsckAHcRhHp5xyBYZTEJqBICPRgw6hPVnpR+nPUv5qFxZqWe5Kqcxz--YDMRhzBTNX1Pj6Gl--bCBc0HuDUfX76AR+FhOldA==
1 change: 1 addition & 0 deletions config/dockerfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ options:
postgresql: true
prepare: false
redis: true
tigris: true
Loading

0 comments on commit 2f27254

Please sign in to comment.