Rails app `fly deploy` failing with ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime

I’m deploying my first app to Fly.io. It is a pretty standard Rails 7 app using Postgres, importmaps, sass, hotwire, and bootstrap.

I’m following the basic Rails guide and when I get to the fly deploy step I see the following error:

 => ERROR [stage-3 7/7] RUN bin/rails fly:build                                                       2.8s
------
 > [stage-3 7/7] RUN bin/rails fly:build:
#20 2.757 rails aborted!
#20 2.757 ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.

On Heroku this app is only using the heroku/ruby buildpack but I believe that includes NodeJS. I’m going to see what the recommended way to add this is and report back. In the meantime, I’d love any suggestions!

Rails applications that use importmaps don’t typically require node.js; so we normally omit that runtime when we detect importmaps. Can you share your Gemfile, or at least identify the gems that you are making use of that make use of node.js. If you do so, I’ll fix flyctl to include nodejs when such gems are included.

I was just about to update this as I was able to get around the issue by adding nodejs to ARG DEPLOY_PACKAGES="postgresql-client file vim curl gzip libsqlite3-0 nodejs" in the # install deployment packages step of the Dockerfile.

Gemfile is as follows:

Gemfile

ruby ‘3.1.2’

gem ‘rails’, ‘~> 7.0’
gem ‘pg’, ‘~> 1.2’
gem ‘puma’
gem ‘jbuilder’

gem ‘bootstrap’, ‘~> 4.0’
gem ‘importmap-rails’
gem ‘sassc-rails’
gem ‘stimulus-rails’
gem ‘turbo-rails’

gem ‘amazing_print’
gem ‘bootsnap’, require: false
gem ‘devise’
gem ‘haml-rails’
gem ‘holidays’
gem ‘iex-ruby-client’
gem ‘lograge’
gem ‘lograge-sql’
gem ‘premailer-rails’
gem ‘retryable’
gem ‘thor’

gem ‘cacheable’
gem ‘connection_pool’
gem ‘redis’

group :production do
gem ‘sentry-rails’
gem ‘sentry-ruby’
end

group :development, :test do
gem ‘bullet’
gem ‘dotenv-rails’
gem ‘factory_bot_rails’
gem ‘letter_opener’
gem ‘pry-byebug’
gem ‘rspec-rails’
end

group :development do
gem ‘array_proc’, require: false
gem ‘bundler-audit’, require: false
gem ‘foreman’, require: false
gem ‘guard’, require: false
gem ‘guard-rspec’, require: false
gem ‘guard-rubocop’, require: false
gem ‘listen’, ‘~> 3.2’
gem ‘rubocop’, require: false
gem ‘rubocop-rails’, require: false
gem ‘rubocop-rspec’, require: false
gem ‘solargraph’, ‘~> 0.39’, require: false
gem ‘string_proc’, require: false
gem ‘web-console’

gem ‘rack-mini-profiler’
gem ‘stackprof’
end

group :test do
gem ‘climate_control’
gem ‘webmock’
end

My next issue is finding out why it’s not properly reading config/credentials/production.yml.enc. I have added the proper key using flyctl with

flyctl secrets unset RAILS_MASTER_KEY
flyctl secrets set RAILS_MASTER_KEY=$(cat config/credentials/production.key)

Edit: fixed the above

The following was because my initializers use secrets. Following the guide at Existing Rails Apps · Fly Docs I updated fly.rake as follows:

  • change task :build => 'assets:precompile' to task :build
  • change task :server => :swapfile do to task server: %i[assets:precompile swapfile] do

I’m pleased to see your progress; but I continue to be puzzled.

I don’t see liquidjs in your Gemfile; perhaps Gemfile.lock would provide some clues as to what is including this. But I do see boostrap. How did you install that? More particularly, do you have a node_modules directory inside your Rails app? Perhaps you installed boostrap with 'npm install -g`?

The code inside flyctl that decides whether nodejs support is to be included:

Note that if flyctl detects nodejs it will make an effort to install the same version of node that you are running on your development machine. Adding nodejs to DEPLOY_PACKAGES will get the version included with the OS (in this case Debian, which looks like it is version 12.

As to master key, that should have been picked up:

1 Like

Bootstrap is installed only thru Gemfile w/ gem 'bootstrap', '~> 4.0'.

FWIW I’m not using any of the JS from Bootstrap, just the CSS and have @import "bootstrap"; in application.scss.

Gemfile.lock is as follows:

Gemfile.lock

GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.4)
actionpack (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activesupport (= 7.0.4)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.4)
actionview (= 7.0.4)
activesupport (= 7.0.4)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.4)
actionpack (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.4)
activesupport (= 7.0.4)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.4)
activesupport (= 7.0.4)
globalid (>= 0.3.6)
activemodel (7.0.4)
activesupport (= 7.0.4)
activerecord (7.0.4)
activemodel (= 7.0.4)
activesupport (= 7.0.4)
activestorage (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activesupport (= 7.0.4)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
amazing_print (1.4.0)
array_proc (1.0.1)
ast (2.4.2)
autoprefixer-rails (10.4.7.0)
execjs (~> 2)
backport (1.2.0)
bcrypt (3.1.18)
benchmark (0.2.0)
bindex (0.8.1)
bootsnap (1.13.0)
msgpack (~> 1.2)
bootstrap (4.6.2)
autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.16.1, < 2)
sassc-rails (>= 2.0.0)
builder (3.2.4)
bullet (7.0.3)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
byebug (11.1.3)
cacheable (2.0.0)
climate_control (1.2.0)
coderay (1.1.3)
concurrent-ruby (1.1.10)
connection_pool (2.3.0)
crack (0.4.5)
rexml
crass (1.0.6)
css_parser (1.12.0)
addressable
devise (4.8.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
diff-lcs (1.5.0)
dotenv (2.8.1)
dotenv-rails (2.8.1)
dotenv (= 2.8.1)
railties (>= 3.2)
e2mmap (0.1.0)
erubi (1.11.0)
execjs (2.8.1)
factory_bot (6.2.1)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
ffi (1.15.5)
foreman (0.87.2)
formatador (1.1.0)
globalid (1.0.0)
activesupport (>= 5.0)
guard (2.18.0)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.13.0)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.7.3)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
guard-rubocop (1.5.0)
guard (~> 2.0)
rubocop (< 2.0)
haml (6.0.7)
temple (>= 0.8.2)
thor
tilt
haml-rails (2.1.0)
actionpack (>= 5.1)
activesupport (>= 5.1)
haml (>= 4.0.6)
railties (>= 5.1)
hashdiff (1.0.1)
hashie (5.0.0)
holidays (8.6.0)
htmlentities (4.3.4)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
iex-ruby-client (2.0.0)
faraday (>= 0.17)
faraday_middleware
hashie
money (~> 6.0)
importmap-rails (1.1.5)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
jaro_winkler (1.5.4)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.6.2)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lograge (0.12.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
lograge-sql (2.0.0)
activerecord (>= 5, < 7.1)
lograge (~> 0.11)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lumberjack (1.2.8)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (1.0.2)
method_source (1.0.0)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.3)
money (6.16.0)
i18n (>= 0.6.4, <= 2)
msgpack (1.6.0)
multipart-post (2.2.3)
nenv (0.3.0)
net-imap (0.3.1)
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.1.3)
timeout
net-smtp (0.3.2)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
orm_adapter (0.5.0)
parallel (1.22.1)
parser (3.1.2.1)
ast (~> 2.4.1)
pg (1.4.4)
popper_js (1.16.1)
premailer (1.18.0)
addressable
css_parser (>= 1.12.0)
htmlentities (>= 4.0.0)
premailer-rails (1.11.1)
actionmailer (>= 3)
premailer (~> 1.7, >= 1.7.9)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
public_suffix (5.0.0)
puma (6.0.0)
nio4r (~> 2.0)
racc (1.6.0)
rack (2.2.4)
rack-mini-profiler (3.0.0)
rack (>= 1.2.0)
rack-test (2.0.2)
rack (>= 1.3)
rails (7.0.4)
actioncable (= 7.0.4)
actionmailbox (= 7.0.4)
actionmailer (= 7.0.4)
actionpack (= 7.0.4)
actiontext (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activemodel (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
bundler (>= 1.15.0)
railties (= 7.0.4)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
railties (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
method_source
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.0.6)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (5.0.5)
redis-client (>= 0.9.0)
redis-client (0.10.0)
connection_pool
regexp_parser (2.6.0)
request_store (1.5.1)
rack (>= 1.4)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
retryable (3.0.5)
reverse_markdown (2.1.1)
nokogiri
rexml (3.2.5)
rspec (3.11.0)
rspec-core (~> 3.11.0)
rspec-expectations (~> 3.11.0)
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-rails (6.0.0)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.11)
rspec-expectations (~> 3.11)
rspec-mocks (~> 3.11)
rspec-support (~> 3.11)
rspec-support (3.11.1)
rubocop (1.36.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.20.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.21.0)
parser (>= 3.1.1.0)
rubocop-rails (2.16.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.13.2)
rubocop (~> 1.33)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
sassc (2.4.0)
ffi (~> 1.9)
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
sentry-rails (5.5.0)
railties (>= 5.0)
sentry-ruby (~> 5.5.0)
sentry-ruby (5.5.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
shellany (0.0.1)
solargraph (0.47.2)
backport (~> 1.2)
benchmark
bundler (>= 1.17.2)
diff-lcs (~> 1.4)
e2mmap
jaro_winkler (~> 1.5)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.1)
parser (~> 3.0)
reverse_markdown (>= 1.0.5, < 3)
rubocop (>= 0.52)
thor (~> 1.0)
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
sprockets (4.1.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
stackprof (0.2.22)
stimulus-rails (1.1.0)
railties (>= 6.0.0)
string_proc (1.0.3)
temple (0.8.2)
thor (1.2.1)
tilt (2.0.11)
timeout (0.3.0)
turbo-rails (1.3.1)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.3.0)
uniform_notifier (1.16.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.18.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.7.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
yard (0.9.28)
webrick (~> 1.7.0)
zeitwerk (2.6.1)

PLATFORMS
ruby

DEPENDENCIES
amazing_print
array_proc
bootsnap
bootstrap (~> 4.0)
bullet
bundler-audit
cacheable
climate_control
connection_pool
devise
dotenv-rails
factory_bot_rails
foreman
guard
guard-rspec
guard-rubocop
haml-rails
holidays
iex-ruby-client
importmap-rails
jbuilder
letter_opener
listen (~> 3.2)
lograge
lograge-sql
pg (~> 1.2)
premailer-rails
pry-byebug
puma
rack-mini-profiler
rails (~> 7.0)
redis
retryable
rspec-rails
rubocop
rubocop-rails
rubocop-rspec
sassc-rails
sentry-rails
sentry-ruby
solargraph (~> 0.39)
stackprof
stimulus-rails
string_proc
thor
turbo-rails
web-console
webmock

RUBY VERSION
ruby 3.1.2p20

BUNDLED WITH
2.3.18

WRT secrets and the master key I updated my previous post as the problem was needing secrets in the initializers. Are secrets’ digests deterministic? I noticed in my app’s Activity screen two entries like the following:

Secret
by {me} about 1 hour
Set secret RAILS_MASTER_KEY
Digest: some_digest_thats_different_in_both_entries

so I’m not sure if I replaced the key with the same thing and got a different digest or if I did need to replace it :man_shrugging:

OK, so boostrap requires autoprefixer-rails which requires execjs which requires nodejs. Your making good progress, so don’t slow down; but I’m going to look into fixing this for future users.

If you have any ideas on how to autodetect my initializers use secrets, let me know and I’ll move assets:precompile to the server step which such use is detected.

Thanks! I think grepping for Rails.application.credentials in /config/initializers would detect this.

I found that I can’t use it in the :server step because then I have OOM crashes :sweat_smile: I tried adding it to :release but then it’s not available at runtime in the image.

My current approach is to add something to ENV during assets:precompile to detect this stage and skip any lines w/ credentials.

Try swapping the order of %i[assets:precompile swapfile].

2 Likes

:raised_hands: That did it! I’ll put my ENV manipulation on hold for now while I figure out how to dump my old DB and import it over here :rocket:

I am up and running! Thanks for you help @rubys! I deleted the app and recreated it in minutes using all these learnings. I can confirm the proper RAILS_MASTER_KEY is set, my confusion was from the initializers in asset precompilation and I can also confirm that removing and setting a new secret will result in a different digest.

I also was able to fix the issue with dumping Heroku’s postgres at Comparing superfly:main...agrberg:patch-1 · superfly/docs · GitHub

We do need a longer term solution for assets:precompile because that now runs every time the server restarts like when adding new secrets :sweat:

I’m going to call it a day for now but I’m going to go back to exploring a way to detect asset compilation in the initializer as the secret isn’t needed then. I still think I can add an ENV var to only that step so it can be detected but I’m definitely open to any suggestions that don’t involve including workarounds in code.

1 Like

I had the same problem.

I solved by adding this to the dockerfile, and making sure I added it just before the line containing fly:build (adding it earlier in the Dockerfile produced the same error).

RUN apt-get update
RUN apt-get install nodejs -y

Hope it helps.

I had the same issue when migrating a Rails 5.2 app from Heroku. To fix it, I just added nodejs to the following line:
ARG DEPLOY_PACKAGES="postgresql-client file vim curl gzip libsqlite3-0" and run fly deploy` again. No other modifications were done.

2 Likes