dockerfile-rails 1.2.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f8b3176721310f5c043a7c2642f12b59fa7bc2952fb73f6e6164d199d24bc5a
4
- data.tar.gz: 995b15167890754208baef9bc6ff48613e8cef121083f8d70c1be77593b5beb4
3
+ metadata.gz: 5d1b0d5e237b0a8c33a3b1263d34590db8912967420863332ad0b2c8f5661e5f
4
+ data.tar.gz: 000fd8db20304bc2ba8623aaa1a4bd19870ec4143cacc3cb6ea31b8690510782
5
5
  SHA512:
6
- metadata.gz: 7f05f65f85a29fcfc242682ab6464adb88f9e0725fffe1c65c192c342e12444c3dbaff9f855ff28ccb94ea13fa706918b71609af06a87673007407083e77dd2c
7
- data.tar.gz: 5bcca34771c67bf29e28be9a0342fbd9dac8a13694f3416d0b27a2070cabf0e38017ed195ecf2b1a80a03150698cb62502e73d63ec603500bb927fa627cd73aa
6
+ metadata.gz: 64b8702f00462a318af21238617528682be3831da82471bff0172ef9ef98d7e4a6a10346ffe297e87e76cc5d4b2699bf56b79d9257f1eee3c8cc24bc56012037
7
+ data.tar.gz: 9a9dc774d388e4276b08bbcdcce9ffd676800df7bb86b4cac34e6795fd0ee6f24ba2a57df393f7948f738a9bdafe8a1ed8073c0b91d9e6a2f9f94349a7d14fe4
data/DEMO.md CHANGED
@@ -132,7 +132,7 @@ docker compose up
132
132
  # Demo 4 - API only
133
133
 
134
134
  This demo deploys a [Create React App](https://create-react-app.dev/) client and a Rails API-only server. Ruby and Rails version information is retrieved from the server and displayed below a spinning React logo. Note that the build process installs the
135
- node moddules and ruby gems in parallel.
135
+ node modules and ruby gems in parallel.
136
136
 
137
137
  ```bash
138
138
  rails new demo --api
data/README.md CHANGED
@@ -40,6 +40,7 @@ bin/rails generate dockerfile
40
40
 
41
41
  * `--ci` - include test gems in deployed image
42
42
  * `--compose` - generate a `docker-compose.yml` file
43
+ * `--max-idle=n` - exit afer *n* seconds of inactivity. Supports [iso 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) and [sleep](https://man7.org/linux/man-pages/man1/sleep.1.html#DESCRIPTION) syntaxes. Uses passenger for now, awaiting [puma](https://github.com/puma/puma/issues/2580) support.
43
44
  * `--nginx` - serve static files via [nginx](https://www.nginx.com/). May require `--root` on some targets to access `/dev/stdout`
44
45
  * `--no-link` - don't add [--link](https://docs.docker.com/engine/reference/builder/#copy---link) to COPY statements. Some tools (like at the moment, [buildah](https://www.redhat.com/en/topics/containers/what-is-buildah)) don't yet support this feature.
45
46
  * `--no-lock` - don't add linux platforms, set `BUNDLE_DEPLOY`, or `--frozen-lockfile`. May be needed at times to work around a [rubygems bug](https://github.com/rubygems/rubygems/issues/6082#issuecomment-1329756343).
@@ -51,6 +52,7 @@ Generally the dockerfile generator will be able to determine what dependencies y
51
52
  are actually using. But should you be using DATABASE_URL, for example, at runtime
52
53
  additional support may be needed:
53
54
 
55
+ * `--litefs` - use [LiteFS](https://fly.io/docs/litefs/)
54
56
  * `--mysql` - add mysql libraries
55
57
  * `--postgresql` - add postgresql libraries
56
58
  * `--redis` - add redis libraries
@@ -65,16 +67,18 @@ Not all of your needs can be determined by scanning your application. For examp
65
67
  * `--env=name:value` - add an environment variable
66
68
  * `--remove package...` - remove package from "to be added" list
67
69
 
68
- Each of these can be tailored to a specific build phase by adding `-base`, `-build`, or `-deploy` after the flag name (e.g `--env-build:`). If no such suffix is found, the default for arg is `-base`, and the default for the rest is `-deploy`. Removal of an arg or environment variable is done by leaving the value blank.
70
+ Each of these can be tailored to a specific build phase by adding `-base`, `-build`, or `-deploy` after the flag name (e.g `--add-build freetds-dev --add-deploy freetds-bin`). If no such suffix is found, the default for arg is `-base`, and the default for the rest is `-deploy`. Removal of an arg or environment variable is done by leaving the value blank (e.g `--env-build=PORT:`).
69
71
 
70
72
  ### Configuration:
71
73
 
72
74
  * `--bin-cd` - adjust binstubs to set current working directory
75
+ [autocrlf](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_core_autocrlf) enabled or may not be able to set bin stubs as executable.
73
76
  * `--label=name:value` - specify docker label. Can be used multiple times. See [LABEL](https://docs.docker.com/engine/reference/builder/#label) for detail
74
77
  * `--no-prepare` - omit `db:prepare`. Useful for cloud platforms with [release](https://devcenter.heroku.com/articles/release-phase) phases
75
78
  * `--platform=s` - specify target platform. See [FROM](https://docs.docker.com/engine/reference/builder/#from) for details
76
79
  * `--precompile=defer` - may be needed when your configuration requires access to secrets that are not available at build time. Results in larger images and slower deployments.
77
80
  * `--root` - run application as root
81
+ * `--windows` - make Dockerfile work for Windows users that may have set `git config --global core.autocrlf true`
78
82
 
79
83
  Options are saved between runs into `config/dockerfile.yml`. To invert a boolean options, add or remove a `no-` prefix from the option name.
80
84
 
@@ -107,9 +111,9 @@ If you are running a single test, the following environment variables settings m
107
111
  * `TEST_CAPTURE=1` will capture test results.
108
112
  * `TEST_KEEP=1` will leave the test app behind for inspection after the test completes.
109
113
 
110
- ## Links
114
+ ## Historical Links
111
115
 
112
- The following links relate to the current development status with respect to Rails 7.1 and will be removed once that is resolved.
116
+ The following links relate to the coordination between this package and Rails 7.1.
113
117
 
114
118
  * [Preparations for Rails 7.1](https://community.fly.io/t/preparations-for-rails-7-1/9512) - [Fly.io](https://fly.io/)'s plans and initial discussions with DHH
115
119
  * [Rails Dockerfile futures](https://discuss.rubyonrails.org/t/rails-dockerfile-futures/82091/1) - rationale for a generator
@@ -118,3 +122,7 @@ The following links relate to the current development status with respect to Rai
118
122
  * Fly.io [Cut over to Rails Dockerfile Generator on Sunday 29 Jan 2023](https://community.fly.io/t/cut-over-to-rails-dockerfile-generator-on-sunday-29-jan-2023/10350)
119
123
  * Fly.io [FAQ](https://fly.io/docs/rails/getting-started/dockerfiles/)
120
124
  * DDH's [target](https://github.com/rails/rails/pull/47372#issuecomment-1438971730)
125
+
126
+ Parallel efforts for Hanami:
127
+
128
+ * [Proposal](https://discourse.hanamirb.org/t/dockerfile-hanami/816)
data/Rakefile CHANGED
@@ -39,7 +39,7 @@ namespace :test do
39
39
  sh "docker run -p 3000:3000 -e RAILS_MASTER_KEY=#{key} --rm system:test"
40
40
  end
41
41
  ensure
42
- rm_rf "test/tmp/system_test"
42
+ rm_rf "test/tmp/system_test" unless ENV["TEST_KEEP"]
43
43
  end
44
44
 
45
45
  task all: %w(test:rubocop test test:system)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "erb"
4
+ require "json"
4
5
  require_relative "../dockerfile-rails/scanner.rb"
5
6
 
6
7
  class DockerfileGenerator < Rails::Generators::Base
@@ -15,10 +16,13 @@ class DockerfileGenerator < Rails::Generators::Base
15
16
  "jemalloc" => false,
16
17
  "label" => {},
17
18
  "link" => true,
19
+ "litefs" => false,
18
20
  "lock" => true,
21
+ "max-idle" => nil,
19
22
  "mysql" => false,
20
23
  "nginx" => false,
21
24
  "parallel" => false,
25
+ "passenger" => false,
22
26
  "platform" => nil,
23
27
  "postgresql" => false,
24
28
  "precompile" => nil,
@@ -28,6 +32,7 @@ class DockerfileGenerator < Rails::Generators::Base
28
32
  "sqlite3" => false,
29
33
  "sudo" => false,
30
34
  "swap" => nil,
35
+ "windows" => false,
31
36
  "yjit" => false,
32
37
  }.then { |hash| Struct.new(*hash.keys.map(&:to_sym)).new(*hash.values) }
33
38
 
@@ -84,6 +89,9 @@ class DockerfileGenerator < Rails::Generators::Base
84
89
  class_option "bin-cd", type: :boolean, default: OPTION_DEFAULTS["bin-cd"],
85
90
  desc: "modify binstubs to set working directory"
86
91
 
92
+ class_option "windows", type: :boolean, default: OPTION_DEFAULTS["windows"],
93
+ desc: "fixup CRLF in binstubs and make each executable"
94
+
87
95
  class_option :cache, type: :boolean, default: OPTION_DEFAULTS.cache,
88
96
  desc: "use build cache to speed up installs"
89
97
 
@@ -105,6 +113,9 @@ class DockerfileGenerator < Rails::Generators::Base
105
113
  class_option :sqlite3, aliases: "--sqlite", type: :boolean, default: OPTION_DEFAULTS.sqlite3,
106
114
  desc: "include sqlite3 libraries"
107
115
 
116
+ class_option :litefs, type: :boolean, default: OPTION_DEFAULTS.litefs,
117
+ desc: "replicate sqlite3 databases using litefs"
118
+
108
119
  class_option :postgresql, aliases: "--postgres", type: :boolean, default: OPTION_DEFAULTS.postgresql,
109
120
  desc: "include postgresql libraries"
110
121
 
@@ -129,6 +140,12 @@ class DockerfileGenerator < Rails::Generators::Base
129
140
  class_option :nginx, type: :boolean, default: OPTION_DEFAULTS.nginx,
130
141
  desc: "Serve static files with nginx"
131
142
 
143
+ class_option :passenger, type: :boolean, default: OPTION_DEFAULTS.passenger,
144
+ desc: "Serve Rails application with Phusion Passsenger"
145
+
146
+ class_option "max-idle", type: :string, default: OPTION_DEFAULTS["max-idle"],
147
+ desc: "Exit server after application has been idle for n seconds."
148
+
132
149
  class_option :root, type: :boolean, default: OPTION_DEFAULTS.root,
133
150
  desc: "Run application as root user"
134
151
 
@@ -221,6 +238,12 @@ class DockerfileGenerator < Rails::Generators::Base
221
238
 
222
239
  template "docker-compose.yml.erb", "docker-compose.yml" if options.compose
223
240
 
241
+ if using_litefs?
242
+ template "litefs.yml.erb", "config/litefs.yml"
243
+
244
+ fly_attach_consul
245
+ end
246
+
224
247
  if @gemfile.include?("vite_ruby")
225
248
  package = JSON.load_file("package.json")
226
249
  unless package.dig("scripts", "build")
@@ -284,12 +307,27 @@ private
284
307
  options.root?
285
308
  end
286
309
 
310
+ def using_litefs?
311
+ options.litefs?
312
+ end
313
+
287
314
  def using_node?
288
315
  return @using_node if @using_node != nil
289
316
  @using_node = File.exist? "package.json"
290
317
  end
291
318
 
292
319
  def using_redis?
320
+ # Note: If you have redis installed on your computer, 'rails new` will
321
+ # automatically add redis to your Gemfile, so having it in your Gemfile is
322
+ # not a reliable indicator of whether or not your application actually uses
323
+ # redis.
324
+
325
+ # using_redis? is currently used for two things: actually adding the redis
326
+ # gem if it is going to be needed in production, and adding a redis
327
+ # container to docker-compose.yml. Neither of these actions should be done
328
+ # unless there is an indication that redis is actually being used and not
329
+ # merely included in the Gemfile.
330
+
293
331
  options.redis? or @redis or @gemfile.include?("sidekiq")
294
332
  end
295
333
 
@@ -301,6 +339,10 @@ private
301
339
  @gemfile.include?("grover") or @gemfile.include?("puppeteer-ruby")
302
340
  end
303
341
 
342
+ def using_passenger?
343
+ options.passenger? or options["max-idle"]
344
+ end
345
+
304
346
  def using_sidekiq?
305
347
  @gemfile.include?("sidekiq")
306
348
  end
@@ -369,7 +411,9 @@ private
369
411
  gems = ["bundler"]
370
412
 
371
413
  if options.ci? && options.lock? && @gemfile.include?("debug")
372
- gems += %w(irb reline) - @gemfile
414
+ # https://github.com/rails/rails/pull/47515
415
+ # https://github.com/rubygems/rubygems/issues/6082#issuecomment-1329756343
416
+ gems += %w(irb reline) - @gemfile unless Gem.ruby_version >= "3.2.2"
373
417
  end
374
418
 
375
419
  gems.sort
@@ -391,7 +435,6 @@ private
391
435
  # libicu63 in buster, libicu67 in bullseye, libiclu72 in bookworm...
392
436
  packages << "libicu-dev" if @gemfile.include? "charlock_holmes"
393
437
 
394
-
395
438
  if @gemfile.include? "webp-ffi"
396
439
  # https://github.com/le0pard/webp-ffi#requirements
397
440
  packages += %w(libjpeg-dev libpng-dev libtiff-dev libwebp-dev)
@@ -464,7 +507,11 @@ private
464
507
  # start with databases: sqlite3, postgres, mysql
465
508
  packages << "libsqlite3-0" if options.sqlite3? || @sqlite3
466
509
  packages << "postgresql-client" if options.postgresql? || @postgresql
467
- packages << "default-mysql-client" if options.mysql || @mysql
510
+ packages << "default-mysql-client" if options.mysql? || @mysql
511
+ packages << "libjemalloc2" if options.jemalloc? && !options.fullstaq?
512
+
513
+ # litefs
514
+ packages += ["ca-certificates", "fuse3", "sudo"] if options.litefs?
468
515
 
469
516
  # ActiveStorage preview support
470
517
  packages << "libvips" if @gemfile.include? "ruby-vips"
@@ -483,8 +530,11 @@ private
483
530
  end
484
531
  end
485
532
 
533
+ # Passenger
534
+ packages += %w(passenger libnginx-mod-http-passenger) if using_passenger?
535
+
486
536
  # nginx
487
- packages << "nginx" if options.nginx?
537
+ packages << "nginx" if options.nginx? || using_passenger?
488
538
 
489
539
  # sudo
490
540
  packages << "sudo" if options.sudo?
@@ -494,18 +544,35 @@ private
494
544
 
495
545
  def deploy_repos
496
546
  repos = []
547
+ packages = []
497
548
 
498
549
  if using_puppeteer? && deploy_packages.include?("google-chrome-stable")
550
+ packages += %w(gnupg curl)
499
551
  repos += [
500
- "curl https://dl-ssl.google.com/linux/linux_signing_key.pub |",
501
- "gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg &&",
552
+ "curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt |",
553
+ " gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg &&",
502
554
  'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
503
555
  ]
504
556
  end
505
557
 
558
+ if using_passenger?
559
+ packages += %w(gnupg curl)
560
+ repos += [
561
+ "curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt |",
562
+ " gpg --dearmor > /etc/apt/trusted.gpg.d/phusion.gpg &&",
563
+ "bash -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger $(source /etc/os-release; echo $VERSION_CODENAME) main > /etc/apt/sources.list.d/passenger.list'"
564
+ ]
565
+ end
566
+
506
567
  if repos.empty?
507
568
  ""
508
569
  else
570
+ packages.sort!.uniq!
571
+ unless packages.empty?
572
+ repos.unshift "apt-get update -qq &&",
573
+ "apt-get install --no-install-recommends -y #{packages.join(" ")} &&"
574
+ end
575
+
509
576
  repos.join(" \\\n ") + " && \\\n "
510
577
  end
511
578
  end
@@ -552,18 +619,26 @@ private
552
619
  def deploy_env
553
620
  env = {}
554
621
 
555
- env["PORT"] = "3001" if options.nginx?
622
+ env["PORT"] = "3001" if (options.nginx? && !using_passenger?) || using_litefs?
556
623
 
557
624
  if Rails::VERSION::MAJOR < 7 || Rails::VERSION::STRING.start_with?("7.0")
558
625
  env["RAILS_LOG_TO_STDOUT"] = "1"
559
626
  env["RAILS_SERVE_STATIC_FILES"] = "true" unless options.nginx?
560
627
  end
561
628
 
629
+ if deploy_database == "sqlite3"
630
+ if using_litefs?
631
+ env["DATABASE_URL"] = "sqlite3:///litefs/production.sqlite3"
632
+ else
633
+ env["DATABASE_URL"] = "sqlite3:///data/production.sqlite3"
634
+ end
635
+ end
636
+
562
637
  if options.yjit?
563
638
  env["RUBY_YJIT_ENABLE"] = "1"
564
639
  end
565
640
 
566
- if options.jemalloc? && (not options.fullstaq?)
641
+ if options.jemalloc? && !options.fullstaq?
567
642
  if (options.platform || Gem::Platform.local.cpu).include? "arm"
568
643
  env["LD_PRELOAD"] = "/usr/lib/aarch64-linux-gnu/libjemalloc.so.2"
569
644
  else
@@ -590,7 +665,7 @@ private
590
665
 
591
666
  env.merge! @@vars["base"] if @@vars["base"]
592
667
 
593
- env.map { |key, value| "#{key}=#{value.inspect}" }
668
+ env.map { |key, value| "#{key}=#{value.inspect}" }.sort
594
669
  end
595
670
 
596
671
  def base_args
@@ -648,13 +723,13 @@ private
648
723
  # none are required, but prepares for the need to do the
649
724
  # fix line endings if other fixups are required.
650
725
  has_cr = Dir["bin/*"].any? { |file| IO.read(file).include? "\r" }
651
- if has_cr || (Gem.win_platform? && !binfixups.empty?)
726
+ if has_cr || (Gem.win_platform? && !binfixups.empty?) || options.windows?
652
727
  binfixups.unshift 'sed -i "s/\r$//g" bin/*'
653
728
  end
654
729
 
655
730
  # Windows file systems may not have the concept of executable.
656
731
  # In such cases, fix up during the build.
657
- unless Dir["bin/*"].all? { |file| File.executable? file }
732
+ if Dir["bin/*"].any? { |file| !File.executable?(file) } || options.windows?
658
733
  binfixups.unshift "chmod +x bin/*"
659
734
  end
660
735
 
@@ -667,9 +742,11 @@ private
667
742
  end
668
743
 
669
744
  def deploy_database
670
- if options.postgresql? || @postgresql
745
+ # note: as database can be overridden at runtime via DATABASE_URL,
746
+ # use presence of "pg" or "mysql2" in the bundle as evidence of intent.
747
+ if options.postgresql? || @postgresql || @gemfile.include?("pg")
671
748
  "postgresql"
672
- elsif options.mysql || @mysql
749
+ elsif options.mysql? || @mysql || @gemfile.include?("mysql2")
673
750
  "mysql"
674
751
  else
675
752
  "sqlite3"
@@ -758,7 +835,11 @@ private
758
835
  end
759
836
 
760
837
  def procfile
761
- if options.nginx?
838
+ if using_passenger?
839
+ {
840
+ nginx: "nginx"
841
+ }
842
+ elsif options.nginx?
762
843
  {
763
844
  nginx: '/usr/sbin/nginx -g "daemon off;"',
764
845
  rails: "./bin/rails server -p 3001"
@@ -782,4 +863,50 @@ private
782
863
 
783
864
  more
784
865
  end
866
+
867
+ def max_idle
868
+ option = options["max-idle"]
869
+
870
+ if option == nil || option.strip.downcase == "infinity"
871
+ nil
872
+ elsif /^\s*\d+(\.\d+)\s*/.match? option
873
+ option.to_f
874
+ elsif /^\s*P/.match? option
875
+ ActiveSupport::Duration.parse(option.strip).seconds
876
+ else
877
+ option.scan(/\d+\w/).map do |t|
878
+ ActiveSupport::Duration.parse("PT#{t.upcase}") rescue ActiveSupport::Duration.parse("P#{t.upcase}")
879
+ end.sum.seconds
880
+ end
881
+ rescue ArgumentError
882
+ nil
883
+ end
884
+
885
+ # if running on fly v2, make a best effort to attach consul
886
+ def fly_attach_consul
887
+ # certainly not fly unless there is a fly.toml
888
+ return unless File.exist? "fly.toml"
889
+
890
+ # Check fly.toml to guess if v1 or v2
891
+ toml = File.read("fly.toml")
892
+ return if toml.include?("enable_consul") # v1-ism
893
+ return unless toml.include?("primary_region") # v2
894
+
895
+ # see if flyctl is in the path
896
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR)
897
+ cmds = %w(flyctl)
898
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
899
+ flyctl = Enumerator.product(paths, cmds, exts).
900
+ map { |path, cmd, ext| File.join(path, "#{cmd}#{ext}") }.
901
+ find { |path| File.executable? path }
902
+ return unless flyctl
903
+
904
+ # see if secret is already set?
905
+ secrets = JSON.parse(`#{flyctl} secrets list --json`)
906
+ return if secrets.any? { |secret| secret["Name"] == "FLY_CONSUL_URL" }
907
+
908
+ # attach consul
909
+ say_status "execute", "flyctl consul attach", :green
910
+ system "#{flyctl} consul attach"
911
+ end
785
912
  end
@@ -134,6 +134,12 @@ RUN SECRET_KEY_BASE<%= Rails::VERSION::MAJOR<7 || Rails::VERSION::STRING.start_w
134
134
  # Final stage for app image
135
135
  FROM base
136
136
 
137
+ <% end -%>
138
+ <% if using_litefs? -%>
139
+ # Install, configure litefs
140
+ COPY --from=flyio/litefs:0.4.0 /usr/local/bin/litefs /usr/local/bin/litefs
141
+ COPY<% if options.link? %> --link<% end %> config/litefs.yml /etc/litefs.yml
142
+
137
143
  <% end -%>
138
144
  <% unless deploy_args.empty? -%>
139
145
  # Deployment build arguments
@@ -145,38 +151,63 @@ ARG <%= deploy_args.map {|key, value| "#{key}=#{value.inspect}"}.join(" \\\n
145
151
  <%= render partial: 'apt_install', locals: {packages: deploy_packages, clean: true, repos: deploy_repos} %>
146
152
  <% end -%>
147
153
 
148
- <% if options.nginx? -%>
154
+ <% if using_passenger? -%>
155
+ <%= render partial: 'passenger' %>
156
+
157
+ <% elsif options.nginx? -%>
149
158
  <%= render partial: 'nginx' %>
150
159
 
151
160
  <% elsif procfile.size > 1 -%>
152
161
  RUN gem install foreman
153
162
 
154
163
  <% end -%>
155
- <% unless run_as_root? -%>
156
- # Run and own the application files as a non-root user for security
164
+ <% unless options.precompile == "defer" -%>
165
+ # Copy built artifacts: gems, application
166
+ COPY --from=build /usr/local/bundle /usr/local/bundle
167
+ COPY --from=build /rails /rails
168
+ <% if api_client_dir -%>
169
+
170
+ # Copy built client
171
+ COPY --from=client /rails/<%= api_client_dir %>/build /rails/public
172
+ <% end -%>
173
+
174
+ <% end -%>
175
+ <% if run_as_root? -%>
176
+ <% if deploy_database == 'sqlite3' -%>
177
+ RUN mkdir /data
178
+ <% end -%>
179
+ <% else -%>
180
+ # Run and own only the runtime files as a non-root user for security
157
181
  <% if options.compose? -%>
158
182
  ARG UID=1000 \
159
183
  GID=1000
160
184
  RUN groupadd -f -g $GID rails && \
161
- useradd -u $UID -g $GID rails<% else -%>
162
- RUN useradd rails<% end -%> --home /rails --shell /bin/bash<% if options.nginx? %> && \
163
- chown rails:rails /var/lib/nginx /var/log/nginx/*<% end %><% if deploy_packages.include?("sudo") && options.sudo? %> && \
185
+ useradd -u $UID -g $GID rails --create-home --shell /bin/bash && \
186
+ <% else -%>
187
+ RUN useradd rails --create-home --shell /bin/bash && \
188
+ <% end -%>
189
+ <% if options.nginx? -%>
190
+ chown rails:rails /var/lib/nginx /var/log/nginx/* && \
191
+ <% end -%>
192
+ <% if deploy_packages.include?("sudo") && options.sudo? -%>
164
193
  sed -i 's/env_reset/env_keep="*"/' /etc/sudoers && \
165
- chown rails:rails .<% end %>
166
- <% unless options.swap -%>
167
- USER rails:rails
168
194
  <% end -%>
169
-
195
+ <% if deploy_database == 'sqlite3' -%>
196
+ mkdir /data<% if using_litefs? %> /litefs<% end %> && \
197
+ chown -R rails:rails <%= Dir[*%w(db log storage tmp)].join(" ") %> /data<% if using_litefs? %> /litefs<% end %>
198
+ <% else -%>
199
+ chown -R rails:rails <%= Dir[*%w(db log storage tmp)].join(" ") %>
200
+ <% end -%>
201
+ <% unless options.swap? or using_passenger? or using_litefs? -%>
202
+ USER rails:rails
170
203
  <% end -%>
171
- <% unless options.precompile == "defer" -%>
172
- # Copy built artifacts: gems, application
173
- COPY --from=build /usr/local/bundle /usr/local/bundle
174
- COPY --from=build <% unless run_as_root? %>--chown=rails:rails <% end %>/rails /rails
175
- <% if api_client_dir -%>
176
204
 
177
- # Copy built client
178
- COPY --from=client <% unless run_as_root? %>--chown=rails:rails <% end %>/rails/<%= api_client_dir %>/build /rails/public
179
205
  <% end -%>
206
+ <% if using_litefs? and !run_as_root? -%>
207
+ # Authorize rails user to launch litefs
208
+ COPY <<-"EOF" /etc/sudoers.d/rails
209
+ rails ALL=(root) /usr/local/bin/litefs
210
+ EOF
180
211
 
181
212
  <% end -%>
182
213
  <% unless deploy_env.empty? -%>
@@ -201,9 +232,15 @@ EOF
201
232
 
202
233
  # Start the server by default, this can be overwritten at runtime
203
234
  EXPOSE 3000
235
+ <% if deploy_database == 'sqlite3' -%>
236
+ VOLUME /data
237
+ <% end -%>
204
238
  CMD ["foreman", "start", "--procfile=Procfile.prod"]
205
239
  <% else -%>
206
240
  # Start the server by default, this can be overwritten at runtime
207
241
  EXPOSE 3000
208
- CMD ["./bin/rails", "server"]
242
+ <% if deploy_database == 'sqlite3' -%>
243
+ VOLUME /data
244
+ <% end -%>
245
+ CMD <%= procfile.values.first.split(" ").inspect %>
209
246
  <% end -%>
@@ -0,0 +1,28 @@
1
+ # configure nginx and passenger
2
+ COPY <<-'EOF' /etc/nginx/sites-enabled/default
3
+ server {
4
+ listen 3000;
5
+ root /rails/public;
6
+ passenger_enabled on;
7
+ <% if options['max-idle'] -%>
8
+ passenger_ctl hook_detached_process /etc/nginx/hook_detached_process;
9
+ passenger_min_instances 0;
10
+ passenger_pool_idle_time <%= max_idle %>;
11
+ <% end -%>
12
+ }
13
+ <% if options['max-idle'] -%>
14
+ COPY <<-'EOF' /etc/nginx/sites-enabled/hook_detached_process
15
+ #!/usr/bin/env ruby
16
+ status = `passenger-status`
17
+ processes = status[/^Processes\s*:\s*(\d*)/, 1].to_i
18
+ system 'nginx -s stop' if processes == 0
19
+ EOF
20
+ <% end -%>
21
+ EOF
22
+ RUN echo "daemon off;" >> /etc/nginx/nginx.conf && \
23
+ sed -i 's/access_log\s.*;/access_log \/dev\/stdout;/' /etc/nginx/nginx.conf && \
24
+ sed -i 's/error_log\s.*;/error_log \/dev\/stderr info;/' /etc/nginx/nginx.conf && \
25
+ <% if options['max-idle'] -%>
26
+ chmod +sx /etc/nginx/sites-enabled/hook_detached_process && \
27
+ <% end -%>
28
+ mkdir /var/run/passenger-instreg
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash -e
2
2
 
3
3
  <% if options.swap -%>
4
- <% if run_as_root? -%>
4
+ <% if run_as_root? or using_passenger? -%>
5
5
  <% @space = "" -%>
6
6
  <% else -%>
7
7
  <% @space = " " -%>
@@ -14,11 +14,21 @@ if [ $UID -eq 0 ]; then
14
14
  <%= @space %>echo 10 > /proc/sys/vm/swappiness
15
15
  <%= @space %>swapon /swapfile
16
16
  <%= @space %>echo 1 > /proc/sys/vm/overcommit_memory
17
- <% unless run_as_root? -%>
17
+ <% if using_litefs? -%>
18
+
19
+ <%= @space %># mount litefs
20
+ <%= @space %>litefs mount &
21
+ <% end -%>
22
+ <% unless run_as_root? or using_passenger? -%>
23
+
18
24
  exec su rails $0 $@
19
25
  fi
20
26
  <% end -%>
21
27
 
28
+ <% elsif using_litefs? -%>
29
+ # mount litefs
30
+ <% unless run_as_root? %>sudo -E <% end %>litefs mount &
31
+
22
32
  <% end -%>
23
33
  <% if options.prepare -%>
24
34
  <% if procfile.size > 1 -%>
@@ -26,7 +36,7 @@ fi
26
36
  if [ "${*}" == "foreman start --procfile=Procfile.prod" ]; then
27
37
  <% else -%>
28
38
  # If running the rails server then create or migrate existing database
29
- if [ "${*}" == "./bin/rails server" ]; then
39
+ if [ "${*}" == <%= procfile.values.first.inspect %> <% if using_litefs? %>-a "$FLY_REGION" == "$PRIMARY_REGION" <%end%>]; then
30
40
  <% end -%>
31
41
  <% if options.precompile == "defer" -%>
32
42
  ./bin/rails assets:precompile
@@ -0,0 +1,116 @@
1
+ # based on: https://github.com/superfly/litefs/blob/main/cmd/litefs/etc/litefs.yml
2
+
3
+ # The FUSE section handles settings on the FUSE file system. FUSE
4
+ # provides a layer for intercepting SQLite transactions on the
5
+ # primary node so they can be shipped to replica nodes transparently.
6
+ fuse:
7
+ # Required. This is the mount directory that applications will
8
+ # use to access their SQLite databases.
9
+ dir: "/litefs"
10
+
11
+ # Set this flag to true to allow non-root users to access mount.
12
+ # You must set the "user_allow_other" option in /etc/fuse.conf first.
13
+ allow-other: false
14
+
15
+ # The debug flag enables debug logging of all FUSE API calls.
16
+ # This will produce a lot of logging. Not for general use.
17
+ debug: false
18
+
19
+ # The data section specifies where internal LiteFS data is stored
20
+ # and how long to retain the transaction files.
21
+ #
22
+ # Transaction files are used to ship changes to replica nodes so
23
+ # they should persist long enough for replicas to retrieve them,
24
+ # even in the face of a short network interruption or a redeploy.
25
+ # Under high load, these files can grow large so it's not advised
26
+ # to extend retention too long.
27
+ data:
28
+ # Path to internal data storage.
29
+ dir: "/data"
30
+
31
+ # Duration to keep LTX files. Latest LTX file is always kept.
32
+ retention: "10m"
33
+
34
+ # Frequency with which to check for LTX files to delete.
35
+ retention-monitor-interval: "1m"
36
+
37
+ # If true, then LiteFS will not wait until the node becomes the
38
+ # primary or connects to the primary before starting the subprocess.
39
+ skip-sync: false
40
+
41
+ # If true, then LiteFS will not exit if there is a validation
42
+ # issue on startup. This can be useful for debugging issues as
43
+ # it avoids constantly restarting the node on ephemeral hosting.
44
+ exit-on-error: false
45
+
46
+ # This section defines settings for the LiteFS HTTP API server.
47
+ # This API server is how nodes communicate with each other.
48
+ http:
49
+ # Specifies the bind address of the HTTP API server.
50
+ addr: ":20202"
51
+
52
+ # This section defines settings for the option HTTP proxy.
53
+ # This proxy can handle primary forwarding & replica consistency
54
+ # for applications that use a single SQLite database.
55
+ proxy:
56
+ # Specifies the bind address of the proxy server.
57
+ addr: ":3000"
58
+
59
+ # The hostport of the target application.
60
+ target: "localhost:3001"
61
+
62
+ # The name of the database used for TXID tracking.
63
+ db: "production.sqlite3"
64
+
65
+ # If true, enables verbose logging of requests by the proxy.
66
+ debug: false
67
+
68
+ # List of paths that are ignored by the proxy. The asterisk is
69
+ # the only available wildcard. These requests are passed
70
+ # through to the target as-is.
71
+ passthrough: []
72
+
73
+ # The lease section defines how LiteFS creates a cluster and
74
+ # implements leader election. For dynamic clusters, use the
75
+ # "consul". This allows the primary to change automatically when
76
+ # the current primary goes down. For a simpler setup, use
77
+ # "static" which assigns a single node to be the primary and does
78
+ # not failover.
79
+ lease:
80
+ # Required. Must be either "consul" or "static".
81
+ type: "consul"
82
+
83
+ # Required. The URL for this node's LiteFS API.
84
+ # Should match HTTP port.
85
+ advertise-url: "http://${HOSTNAME}.vm.${FLY_APP_NAME}.internal:20202"
86
+
87
+ # Specifies whether the node can become the primary. If using
88
+ # "static" leasing, this should be set to true on the primary
89
+ # and false on the replicas.
90
+ candidate: ${FLY_REGION == PRIMARY_REGION}
91
+
92
+ # A Consul server provides leader election and ensures that the
93
+ # responsibility of the primary node can be moved in the event
94
+ # of a deployment or a failure.
95
+ consul:
96
+ # Required. The base URL of the Consul server.
97
+ url: "${FLY_CONSUL_URL}"
98
+
99
+ # Required. The key used for obtaining a lease by the primary.
100
+ # This must be unique for each cluster of LiteFS servers
101
+ key: "litefs/${FLY_APP_NAME}"
102
+
103
+ # Length of time before a lease expires. The primary will
104
+ # automatically renew the lease while it is alive, however,
105
+ # if it fails to renew in time then a new primary may be
106
+ # elected after the TTL. This only occurs for unexpected loss
107
+ # of the leader as normal operation will allow the leader to
108
+ # handoff the lease to another replica without downtime.
109
+ #
110
+ # Consul does not allow a TTL of less than 10 seconds.
111
+ ttl: "10s"
112
+
113
+ # Length of time after the lease expires before a candidate
114
+ # can become leader. This buffer is intended to prevent
115
+ # overlap in leadership due to clock skew or in-flight calls.
116
+ lock-delay: "1s"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dockerfile-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-05 00:00:00.000000000 Z
11
+ date: 2023-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -44,16 +44,18 @@ files:
44
44
  - lib/generators/templates/_nginx.erb
45
45
  - lib/generators/templates/_node_client.erb
46
46
  - lib/generators/templates/_npm_install.erb
47
+ - lib/generators/templates/_passenger.erb
47
48
  - lib/generators/templates/docker-compose.yml.erb
48
49
  - lib/generators/templates/docker-entrypoint.erb
49
50
  - lib/generators/templates/dockerfile.yml.erb
50
51
  - lib/generators/templates/dockerignore.erb
52
+ - lib/generators/templates/litefs.yml.erb
51
53
  - lib/generators/templates/node-version.erb
52
- homepage: https://github.com/rubys/dockerfile-rails
54
+ homepage: https://github.com/fly-apps/dockerfile-rails
53
55
  licenses:
54
56
  - MIT
55
57
  metadata:
56
- homepage_uri: https://github.com/rubys/dockerfile-rails
58
+ homepage_uri: https://github.com/fly-apps/dockerfile-rails
57
59
  post_install_message:
58
60
  rdoc_options: []
59
61
  require_paths:
@@ -69,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
71
  - !ruby/object:Gem::Version
70
72
  version: '0'
71
73
  requirements: []
72
- rubygems_version: 3.4.6
74
+ rubygems_version: 3.4.8
73
75
  signing_key:
74
76
  specification_version: 4
75
77
  summary: Dockerfile generator for Rails