How do I run `PORT=1234 npx y-websocket` in a multiprocess?

I am using Rails for the backend, but I want to use websocket on the frontend, so I want to launch websocket in a multi-process.
For websocket, I use yjs-websocket.

By the way in a local environment, use the following:

# console
HOST=localhost PORT=1234 npx y-websocket
# Javascript Code
const wsProvider = new WebsocketProvider('ws://localhost:1234', 'my-roomname', doc)

I implemented fly.toml using this page as a reference.

# fly.toml app configuration file generated for bookrin on 2025-03-06T21:28:42+09:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'bookrin'
primary_region = 'nrt'
console_command = '/rails/bin/rails console'

[deploy]
  release_command = "./bin/rails db:prepare"

[processes]
  web = "bin/rails server"
  websocket = "PORT=1234 npx y-websocket"

[[services]]
  processes = ["web"]
  internal_port = 3000
  protocol = "tcp"
  auto_start_machines = true
  min_machines_running = 0

  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

  [[vm]]
    processes = ["web"]
    memory = "512mb"
    cpu_kind = "shared"
    cpus = 1

  [services.concurrency]
    type = "connections"
    hard_limit = 25
    soft_limit = 20

[[services]]
  processes = ["websocket"]
  internal_port = 1234
  protocol = "tcp"

  [[services.ports]]
    port = 1234
    handlers = ["tls"]

[[statics]]
  guest_path = "/rails/public"
  url_prefix = "/"

The Javascript code is trying to make a connection using the following URL:

wss://bookrin.fly.dev:1234

But the connection fails.

WebSocket connection to 'wss://bookrin.fly.dev:1234/2' failed

※The yjs-websocket specification includes the ID of the resource (noteId) in the URL path (/2).

Also, when I look at status, the websocket machine has stopped.

❯ fly status
App
  Name     = bookrin
  Owner    = personal
  Hostname = bookrin.fly.dev
  Image    = bookrin:deployment-01JP7Q1VPS9Y5ZY8FR2W4F7HKT

Machines
PROCESS         ID              VERSION REGION  STATE   ROLE    CHECKS  LAST UPDATED
web             2867657a959e18  43      nrt     started                 2025-03-13T12:20:16Z
web             4d89440b013768  43      nrt     started                 2025-03-13T12:19:53Z
websocket       080e6e6b011e78  43      nrt     stopped                 2025-03-13T12:34:37Z
websocket       e784770a409758  43      nrt     stopped                 2025-03-13T12:33:55Z

In any case, I am wondering if flytoml is not writing incorrectly.
Is the toml correct?

Finally, I will post the log at the time of deploy.

❯ fly deploy
==> Verifying app config
Validating /home/masyuko0222/BookRIn/fly.toml
✓ Configuration is valid
--> Verified app config
==> Building image
==> Building image with Depot
--> build:
[+] Building 8.9s (22/23)
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 2.34kB                                                                             0.0s
 => resolve image config for docker-image://docker.io/docker/dockerfile:1                                          1.4s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:4c68376a702446fc3c79af22de146a148bc3367e73c25a5803  0.0s
 => => resolve docker.io/docker/dockerfile:1@sha256:4c68376a702446fc3c79af22de146a148bc3367e73c25a5803d453b6b3f72  0.0s
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => Deduplicating step ID [internal] load build definition from Dockerfile, another build is calculating it     0.0s
 => [internal] load metadata for docker.io/library/ruby:3.3.0-slim                                                 0.6s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 916B                                                                                  0.0s
 => [internal] load build context                                                                                  6.7s
 => => transferring context: 11.19kB                                                                               0.0s
 => [base 1/4] FROM docker.io/library/ruby:3.3.0-slim@sha256:d19b39b55078ce9d52c6c6f5ab2aee41b17bee4e18c0bfc734ec  0.0s
 => => resolve docker.io/library/ruby:3.3.0-slim@sha256:d19b39b55078ce9d52c6c6f5ab2aee41b17bee4e18c0bfc734eceaa6f  0.0s
 => CACHED [base 2/4] WORKDIR /rails                                                                               0.0s
 => CACHED [base 3/4] RUN gem update --system --no-document &&     gem install -N bundler                          0.0s
 => CACHED [base 4/4] RUN apt-get update -qq &&     apt-get install --no-install-recommends -y curl libjemalloc2   0.0s
 => CACHED [build 1/8] RUN apt-get update -qq &&     apt-get install --no-install-recommends -y build-essential g  0.0s
 => CACHED [build 2/8] RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/   0.0s
 => CACHED [build 3/8] COPY Gemfile Gemfile.lock ./                                                                0.0s
 => CACHED [build 4/8] RUN bundle install &&     rm -rf ~/.bundle/ "/usr/local/bundle"/ruby/*/cache "/usr/local/b  0.0s
 => CACHED [build 5/8] COPY package.json package-lock.json ./                                                      0.0s
 => CACHED [build 6/8] RUN npm install                                                                             0.0s
 => CACHED [build 7/8] COPY . .                                                                                    0.0s
 => CACHED [build 8/8] RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile                                   0.0s
 => CACHED [stage-2 1/3] COPY --from=build /usr/local/bundle /usr/local/bundle                                     0.0s
 => CACHED [stage-2 2/3] COPY --from=build /rails /rails                                                           0.0s
 => CACHED [stage-2 3/3] RUN groupadd --system --gid 1000 rails &&     useradd rails --uid 1000 --gid 1000 --crea  0.0s
 => exporting to image                                                                                             6.6s
 => => exporting layers                                                                                            0.0s
 => => exporting manifest sha256:db2b0aeded627682601d3a9965b1b8ca653a7272ccbf25bb5418d55abda4a60a                  0.0s
 => => exporting config sha256:9201143cd05e1fb565bd845a0fdcb4f7a8457c90acbf5eb83e2c18927377daef                    0.0s
 => => pushing layers for registry.fly.io/bookrin:deployment-01JP7Q1VPS9Y5ZY8FR2W4F7HKT@sha256:db2b0aeded62768260  2.5s
 => => pushing layer sha256:09d9615e27d104b2588b7a2cd7b1de9fc7ffce5c4950ea75b26eb84bb2b07f69                       1.1s
 => => pushing layer sha256:06048514239bdc828f40e945a528f9b4128206877a35d610e2ffbbbe0266a048                       1.2s
 => => pushing layer sha256:dacfb865edcf5fa754a3f773788669c4fb4ea5687ee39c69a9cbd105a11edc93                       1.1s
 => => pushing layer sha256:1a4fc4a49bd0d626fd08a56aafc8a0f50304e05468f7caec6d01415acb3f356a                       2.4s
 => => pushing layer sha256:2c8a02c3e8466680e2bd03c349b1d4d279abc24c1db3eca914384daacb229a7d                       1.1s
 => => pushing layer sha256:41ae0a46e1ed4f07a94e740fb03bf6835c8e8aa11e68137226db1e0033655701                       1.3s
 => => pushing layer sha256:53c6cbbed68ded6a5c110b14c6dcc160483913e9f1a676f8b0df34efdd5a36cf                       1.1s
 => => pushing layer sha256:9201143cd05e1fb565bd845a0fdcb4f7a8457c90acbf5eb83e2c18927377daef                       2.5s
 => => pushing layer sha256:ca28790d4c71da258d982dc96094aa10ee250c59eec4f5b3e21d67a350d21d8b                       2.4s
 => => pushing layer sha256:13808c22b207b066ef43572e57e4fb8c6172e887dd9a918c089a174a19371b7a                       1.2s
 => => pushing layer sha256:f02cb7e67d4f9403d98fa0d5dab62fe943537f7a4f99cbf90575493347b3567d                       1.1s
 => => pushing layer sha256:0f9ee1b830233dc20931cf41e7d927ae3f48e8a4ef2782ecdd74059c9729abaf                       1.1s
 => => pushing manifest for registry.fly.io/bookrin:deployment-01JP7Q1VPS9Y5ZY8FR2W4F7HKT@sha256:db2b0aeded627682  4.1s
--> Build Summary:
--> Building image done
image: registry.fly.io/bookrin:deployment-01JP7Q1VPS9Y5ZY8FR2W4F7HKT
image size: 173 MB

Watch your deployment at https://fly.io/apps/bookrin/monitoring

Running bookrin release_command: ./bin/rails db:prepare
Starting machine

-------
 ✔ release_command 6e82779b017258 completed successfully
-------
Process groups have changed. This will:
 * create 2 "websocket" machines

No machines in group websocket, launching a new machine
Creating a second machine to increase service availability
Finished launching new machines
-------
 ✔ Machine e784770a409758 [websocket] update finished: success
-------
Updating existing machines in 'bookrin' with rolling strategy

WARNING The app is not listening on the expected address and will not be reachable by fly-proxy.
You can fix this by configuring your app to listen on the following addresses:
  - 0.0.0.0:3000d lease for 2867657a959e18
Found these processes inside the machine with open listening sockets:
  PROCESS        | ADDRESSES
-----------------*-----------------------------------------
  /.fly/hallpass | [fdaa:11:56bb:a7b:d86c:fc8d:d810:2]:22


-------
 ✔ [1/2] Cleared lease for 4d89440b013768
 ✔ [2/2] Cleared lease for 2867657a959e18
-------
Checking DNS configuration for bookrin.fly.dev

Visit your newly deployed app at https://bookrin.fly.dev/

You probably don’t want fly io processes (which are on separate machines) but rather operating system processes. See: Multiple processes inside a Fly.io app · Fly Docs

Unless you are IPv6 only, you also will need a dedicated IPv4 address: Public Network Services · Fly Docs

Everything else looks fine.

I agree with @rubys here; your listeners are largely doing the same thing, and it conceptually makes sense to put them in the same machines.

I have a queue doing four different kinds of processing task, and each task is a separate Supervisord entry. In the same way as your situation, it is not necessary to run them in separate machines.

@rubys @halfer
Thank you for your replies.
I am new to Supervisord and decided to use Procfile and overmind.

  • Procfile
web: bin/rails server
ws: "PORT=1234 npx y-websocket"
  • Docker
# syntax=docker/dockerfile:1
# check=error=true

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.3.0
FROM ruby:$RUBY_VERSION-slim AS base

LABEL fly_launch_runtime="rails"

# Rails app lives here
WORKDIR /rails

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

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

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


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

# Install packages needed to build gems and node modules
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git libpq-dev libyaml-dev node-gyp pkg-config python-is-python3 nodejs npm tmux && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install Node.js
ARG NODE_VERSION=20.12.2
ENV PATH=/usr/local/node/bin:$PATH
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
    /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
    rm -rf /tmp/node-build-master

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

# Install node modules
COPY package.json package-lock.json ./
RUN npm install

# Copy application code
COPY . .

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile


# Final stage for app image
FROM base


# 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 1000:1000 db log tmp
USER 1000:1000

# use Procfile
RUN gem install --user-install overmind
ADD Procfile /app/
CMD ["bash", "-c", "export PATH=$HOME/.gem/ruby/3.3.0/bin:$PATH && overmind start"]

I also recognize that the machine will be created by the processes in fly.toml, so I removed the processes from toml and wrote the following.

# fly.toml app configuration file generated for bookrin on 2025-03-06T21:28:42+09:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'bookrin'
primary_region = 'nrt'
console_command = '/rails/bin/rails console'

[deploy]
  release_command = "./bin/rails db:prepare"

[[vm]]
  # Ruby3.3に必要なメモリ
  # https://community.fly.io/t/dockerfile-help-for-ruby-3-3-0/17700
  memory = "512mb"
  cpu_kind = "shared"
  cpus = 1

[[services]]
  internal_port = 3000
  protocol = "tcp"
  auto_start_machines = true
  min_machines_running = 0

  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]
  
  [services.concurrency]
    type = "connections"
    hard_limit = 25
    soft_limit = 20

[[services]]
  internal_port = 1234
  protocol = "tcp"

  [[services.ports]]
    port = 1234
    handlers = ["tls"]

[[statics]]
  guest_path = "/rails/public"
  url_prefix = "/"

However, the machine is not working properly and I cannot access the application.

❯ fly status
Machines
PROCESS ID              VERSION REGION  STATE   ROLE    CHECKS  LAST UPDATED
app     784e2e3f649418  48      nrt     stopped                 2025-03-14T17:19:20Z
app     784e667bd169e8  48      nrt     stopped                 2025-03-14T17:20:09Z

Is this an incorrectly written flytoml?

Also, @rubys mentioned the following, did you mean that this IPv4 needs to be configured for websocket communication?

Unless you are IPv6 only, you also will need a dedicated IPv4 address: Public Network Services · Fly Docs

The service itself (https://my-app.fly.dev) seems to be assigned Shared v4, as seen on the Dashboard.

I am a little confused because deploying is a new act for me.

Consider running your container locally first in Docker, to see:

  • Does the container stay up?
  • Do the docker logs <container> logs indicate no issues?
  • Can you shell into the container with docker exec -it <container> sh?
  • Can you issue ps aux to see if your process supervisor is alive?
  • Can you issue the same commands to see if your processes are alive?

Shared ipv4 only works for HTTP/HTTPS over ports 80 and 443. No ipv4 traffic will reach your yjs-websocket application unless you change that to a dedicated ipv4 address. Instructions to do so can be found here: Public Network Services · Fly Docs

Thanks for the reply.
Actually, I am not an engineer right now, only learning, so I just ended up writing a DockerFile using GPT, etc.
I would like to read the documentation a little more and then write a Docker File again.

1 Like

Perhaps this will be useful: Cookbooks · Fly Docs ?

Thank you for your kindness!
I will have a hands-on session and then I will read the documentation.

I would like to ask about “dedicated ipv4”.
In the documentation, I find the following statement

Shared IPv4 addresses are recommended unless you have an explicit need for your own IP. Some of the reasons to use a dedicated IPv4 address include:
Your app uses a non-HTTP protocol that doesn’t use TLS.

For example, if I use a URL like wss://xxx, I recognize that it is WebSocket over SSL/TLS, that is, a non-HTTP but TLS-based protocol.

Therefore, I thought that Dedicated IPv4 is not necessary, but what do you think?

Websockets over port 80 or 443 are fine with a shared IP.

1 Like

@rubys @halfer
I was able to move it safely. Thank you very much.

@rubys @halfer
I was able to move it safely. Thank you very much.

I will describe the code below and hope it will be helpful to others in the same situation.

  • supervisord.conf
[supervisord]
logfile=/dev/stdout 
logfile_maxbytes=0  
loglevel=info
pidfile=/tmp/supervisord.pid
nodaemon=true

[program:rails]
command=./bin/rails server

[program:ws]
command=bash -c "HOST=0.0.0.0 PORT=1234 npx y-websocket"
  • fly.toml
# fly.toml app configuration file generated for bookrin on 2025-03-15T23:24:38+09:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'bookrin'
primary_region = 'nrt'
console_command = '/rails/bin/rails console'

[deploy]
  release_command = './bin/rails db:prepare'

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = "off"
  auto_start_machines = true
  min_machines_running = 0

  [http_service.concurrency]
    type = "requests"
    soft_limit = 200
    hard_limit = 250

[[services]]
  internal_port = 1234
  protocol = "tcp"

  [[services.ports]]
    port = 1234
    handlers = ["tls"]

[[vm]]
  memory = '512mb'
  cpu_kind = 'shared'
  cpus = 1

[[statics]]
  guest_path = '/rails/public'
  url_prefix = '/'
  • Docker
# syntax=docker/dockerfile:1
# check=error=true

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.2
FROM ruby:$RUBY_VERSION-slim AS base

LABEL fly_launch_runtime="rails"

# Rails app lives here
WORKDIR /rails

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

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

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


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

# Install packages needed to build gems and node modules
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git libpq-dev libyaml-dev node-gyp pkg-config python-is-python3 && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install Node.js
ARG NODE_VERSION=20.12.2
ENV PATH=/usr/local/node/bin:$PATH
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
    /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
    rm -rf /tmp/node-build-master

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

# Install node modules
COPY package.json package-lock.json ./
RUN npm install

# Copy application code
COPY . .

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile


# Final stage for app image
FROM base


# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# 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 1000:1000 db log tmp

CMD ["/usr/bin/supervisord"]
  • websocket instance(in JS)
const wsUrl = railsEnv === 'production' ? 'wss://bookrin.fly.dev:1234/' : 'ws://localhost:5678';