Writebook - Once

Hi!
Today im trying to deploy Writebook a free and polish DIY reading book platform.

Unfortunately they dont provide instructions on how to deploy in fly and they have an interactive terminal set up. Fly.io doesnt cope very well with this out of the box since thats not how you are suppose to launch application on fly.

A turnaround is to just deploy the equivalent to a droplet from digital ocean in fly.io (aka empty fly machine) however im not very experience with the fly cli and couldnt find information about in the docs.

How should I deploy a persistent empty fly machine?
I also dont know that much about dockerfiles but I gave it a shot:

FROM debian:bullseye-slim

# Install required dependencies
RUN apt-get update && apt-get install -y \
    curl \
    ca-certificates \
    gnupg \
    lsb-release \
    sudo \
    openssh-server \
    vim \
    nano \
    wget \
    git \
    && rm -rf /var/lib/apt/lists/*

# Create directory for Writebook data
RUN mkdir -p /data

# Expose the ports that Writebook runs on
EXPOSE 80 443 22
# fly.toml app configuration file generated for once-empty on 2025-03-13T12:03:27-06:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'once-empty'
primary_region = 'qro'

[build]
  dockerfile = 'Dockerfile'

[[vm]]
  memory = '2gb'
  cpu_kind = 'shared'
  cpus = 1

What am i doing wrong and how to solve it?

i did deployed an empty debian image :
fly machine run debian:bullseye-slim --app once-empty

However it errored with:

2025-03-13T18:21:08.284 app[2874431a073e28] qro [info] INFO Starting init (commit: d15e62a13)...

2025-03-13T18:21:08.424 app[2874431a073e28] qro [info] INFO Preparing to run: `bash` as root

2025-03-13T18:21:08.426 app[2874431a073e28] qro [info] INFO [fly api proxy] listening at /.fly/api

2025-03-13T18:21:08.448 runner[2874431a073e28] qro [info] Machine started in 1.06s

2025-03-13T18:21:08.621 app[2874431a073e28] qro [info] 2025/03/13 18:21:08 INFO SSH listening listen_address=[fdaa:9:4fd:a7b:d6:b3e1:c119:2]:22

2025-03-13T18:21:09.433 app[2874431a073e28] qro [info] INFO Main child exited normally with code: 0

2025-03-13T18:21:09.448 app[2874431a073e28] qro [info] INFO Starting clean up.

2025-03-13T18:21:09.460 app[2874431a073e28] qro [info] WARN could not unmount /rootfs: EINVAL: Invalid argument

2025-03-13T18:21:09.461 app[2874431a073e28] qro [info] [ 1.927695] reboot: Restarting system

2025-03-13T18:21:09.926 runner[2874431a073e28] qro [info] machine exited with exit code 0, not restarting

So I tried another approach.

  1. Create an empty application with fly apps create once-empty
  2. Put a machine a debian image inside the application with fly machine run debian:bullseye-slim --app once-empty

The app did deployed but the machine doesnt start at all.
The logs are the same as the above

2025-03-13T18:43:39.292 app[784e1d9cd76278] qro [info] 2025-03-13T18:43:39.292653897 [01JP8CS5HEYG58165E3NBBP5ZG:main] Running Firecracker v1.7.0

2025-03-13T18:43:40.195 app[784e1d9cd76278] qro [info] INFO Starting init (commit: d15e62a13)...

2025-03-13T18:43:40.367 app[784e1d9cd76278] qro [info] INFO Preparing to run: `bash` as root

2025-03-13T18:43:40.369 app[784e1d9cd76278] qro [info] INFO [fly api proxy] listening at /.fly/api

2025-03-13T18:43:40.411 runner[784e1d9cd76278] qro [info] Machine started in 1.187s

2025-03-13T18:43:40.579 app[784e1d9cd76278] qro [info] 2025/03/13 18:43:40 INFO SSH listening listen_address=[fdaa:9:4fd:a7b:1ab:d6ba:1c6c:2]:22

2025-03-13T18:43:41.374 app[784e1d9cd76278] qro [info] INFO Main child exited normally with code: 0

2025-03-13T18:43:41.390 app[784e1d9cd76278] qro [info] INFO Starting clean up.

2025-03-13T18:43:41.401 app[784e1d9cd76278] qro [info] WARN could not unmount /rootfs: EINVAL: Invalid argument

2025-03-13T18:43:41.402 app[784e1d9cd76278] qro [info] [ 2.038430] reboot: Restarting system

2025-03-13T18:43:41.631 runner[784e1d9cd76278] qro [info] machine exited with exit code 0, not restarting

Hi… The Machine is starting but then exiting immediately, since you didn’t give it anything to do.

Overall, the approach that you’re trying to take isn’t going to work. (There is no “persistent empty machine” on the Fly.io platform.) You’ll need to put all of the installation procedure into the Dockerfile.

Hope this gets things pointed in the right direction!

1 Like

it looks like a Docker image is available after you get a free licence so get and deploy that image.

From ONCE — Writebook

you can also run Writebook directly via Docker on your internal network

wish. They dont - the email you receive after you sign up is

Hey there,

Great to hear you’re interested in trying out Writebook. Writebook is brought to you by 37signals (the makers of Basecamp, HEY Email + Calendar, and ONCE).

Since Writebook is something you need to install and host yourself, you’ll need a few things before you begin:

  1. Your own domain name.
  2. A web server connected to the internet.
  3. Some basic technical know-how.

While Writebook isn’t difficult to set up, if you’re not a technical person you might want to get a techy friend to help.

Ok, let’s get into it.

Here’s how to get it running on your end:

  1. FIRST, pick a machine to host Writebook. If you need one in the cloud, we recommend checking out DigitalOcean (instructions) or Hetzner (instructions).
  2. THEN, point DNS to the IP address of the machine that’ll be hosting Writebook. You need to point a domain (example.com) or subdomain (writebook.example.com) to the IP address of the machine hosting Writebook. Make sure it’s a straight DNS pointer, no proxying! (Don’t worry about SSL, Writebook will automatically set that up for you).
  3. NEXT, connect a terminal to the machine. To run the install command, you must connect to the machine you’re using with either SSH or a web-based cloud console.
  4. LAST, install Writebook with one simple command. Paste the following into the terminal on your server and wait while everything is installed (this may take up to 5 minutes).

/bin/bash -c “$(curl -fsSL https://auth.once.com/install/[LICENSE-KEY])”

IMPORTANT NOTE: Do not give this install command out to anyone, or share it on the public internet. This is personalized to you, and the license is tied back to you. Your personalized purchase token is [LICENSE-KEY].

Running this command will automatically install Docker on your server if you’re using Linux (which is what everything in the cloud usually runs), then download the latest Writebook application as a container that can run on top of Docker. In the process, it’ll ask for the domain name you’re using to host Writebook, so that we can configure an SSL certificate for you.

That’s it! Now you’re ready to setup the first user on the new installation. Go to the https://YOUR-DOMAIN and the process will begin. Then you’ll be ready to invite the rest of your team to the system.

Your Writebook installation will automatically update to the latest version every night at 2am (local time of your server). You can turn this off via the once command. You can also use this command to take a backup of your data, reset a password, and several other administrative functions. Just connect a terminal to the machine again and run the once command to see all the options.

Enjoy publishing with Writebook!

P.S. If you’d like to run multiple installations of Writebook, you’ll need to get one license per installation/domain.

@mayailurus is quite right, you need to add a CMD to your Dockerfile.

I certainly would not expect a software application/platform to give instructions on how to deploy on Fly. AWS/Azure/GCP maybe, but not tiny/new platforms.

A good rule of thumb is: can it be made to work locally in Docker locally? Since that won’t work on your laptop, it also won’t work in Fly. There will be a command that causes the listener to start up, e.g. Apache, Express, Nginx, etc. Often these are set in “foreground” mode so that they block control returning to the console (which is what causes the container to exit in your case).

1 Like

I sent an email asking for the docker image (because the website indeed says should be available after getting the license). They did explain how to get the image:

# Direct Docker Image Setup Instructions

Thanks for reaching out! Fetching the Docker image directly is not documented at the moment, but you can do it. This isn't the setup that we officially support, but you are of course welcome to run it however you like, keeping in mind that Writebook and all our Once products come with bare-bones support.

To get the image, you can authenticate into our registry using the email address of your purchase, and the personalized purchase token from the email you received when you bought it. That is effectively what the once installer does. Once authenticated, you can pull the image registry.once.com/writebook. So it would look something like this:

**bash
docker login registry.once.com -u youremail@emailaddress.com
# {enter your purchase token}
docker pull registry.once.com/writebook
*** 

To run the container you'll also need to set a few environment variables:

SECRET_KEY_BASE - a random string, unique to your deployment used to generate installation-specific secrets like cookies
SSL_DOMAIN - set this if you want the container to provision its own SSL certificate; leave unset if you will terminate SSL elsewhere
VAPID_PUBLIC_KEY & VAPID_PRIVATE_KEY - a keypair used to send Web Push notifications. You can generate these with something like https://vapidkeys.com

To clarify, this isn't our supported method of installation, but should work. I hope this helps!

Now the question is how do you pull images from a private registry and push them to fly?

Great stuff. You can use this format (untested):

docker run \
    -e SECRET_KEY_BASE=xxx \
    -e SSL_DOMAIN=xxx \
    -e VAPID_PUBLIC_KEY=xxx \
    -e VAPID_PRIVATE_KEY=xxx \
    registry.once.com/writebook

Try it locally first (though if it needs access to LetsEncrypt it may fail).

That is explained in the email you have posted.

On a general note, I can’t see how this container does storage. One of the things that Fly stresses is that storage (volumes and databases) need to have redundancy. This is something you get for free on other hosts, but not so much on Fly.

If the physical hardware fails for one of your machines, you should have any data on that machine already replicated on other machines. For example, LiteFS does that for a SQLite database if you implement it.

If this system connects to the cloud and stores data there, then great. However that would make a software installation pointless. Can you find out more about this? It may be worth looking at that before spending too much time on implementation.

1 Like

Good point. Is a ruby on rails app.

Looking at the source code (which is more or less the whole app except has some key missing variables like a redis url) they use a sqlite database inside the app.

I believe fly offers a way to push your images instead of leaving the fly cli to build them. That said im not so sure how to attach a volume after push the image since I have zero experience working with ruby apps lol

Ah, if it is SQLite then you might get away with installing LiteFS and then setting up the database credentials/path to make use of that. But I don’t know how much Rails will need to use a LiteFS driver, or whether it can blithely carry on using the default SQLite driver while still ensuring that multi-machine replication happens in the background over the private network.

isnt litefs a paid software with license?

No.

1 Like

If it is a Ruby on Rails app, you should be able to just run fly launch. If you don’t have a Dockerfile, it will produce one for you.

Already tried that.
Doesnt work. Im not familiar with ruby on rails app but the error threw something about health checks not passed and missing variables on start.
Additional context: The application has an interactive set up via terminal after building the image. Its not possible to go through this set up bc fly only allow you to watch the logs building the image - not interact with them (as far as i know i may be wrong)

Since the image continue with out this set up the application always errors

Care to provide more details? The health checks not passing is likely due to the missing variables.

Sorry for the late update, i forgot to add reply to your comment and then the system banned me for a little while because i tried to repost the message

update: So I’ve been trying with two approaches. here is my progress on both so far

From private registry to fly

  1. Confirm I had access to the private registry and pull the image
  2. Once i got the image from the private registry: docker tag registry.once.com/writebook:latest registry.fly.io/once-writebook:latest
  3. fly auth docker
  4. docker push registry.fly.io/once-writebook:latest
  5. flyctl deploy -i registry.fly.io/once-writebook:latest
  6. Error: the config for your app is missing an app name, add an app field to the fly.toml file or specify with the -a flag
  7. After this error I tried with a toml file
app = 'once-writebook'
primary_region = 'qro'

[build]
  # Note: tried with the value once-writebook:latest before here too
  image = 'registry.fly.io/once-writebook:latest' 

[[mounts]]
  source = 'writebook_data'
  destination = '/data'

[http_service]
  internal_port = 3000
  force_https = true

[[vm]]
  memory = '1gb'
  cpu_kind = 'shared'
  cpus = 1

Set env variables

fly secrets set SECRET_KEY_BASE=$(openssl rand -hex 32)
fly secrets set VAPID_PRIVATE_KEY=[key from https://vapidkeys.com]
fly secrets set VAPID_PUBLIC_KEY=[key from https://vapidkeys.com]

Result: Error: failed to fetch an image or build from source: Could not find image "registry.fly.io/once-writebook:latest"

Deploy ruby rails app from open source code

  1. Got the app on a directory
  2. fly launch
    Error:
fly launch
Scanning source code
Detected a Rails app
Creating app in /Users//Documents/GitHub/once_book_publishing/writebook-ruby-app
We're about to launch your Rails app on Fly.io. Here's what you're getting:

Organization: Outreach Agency                                            (fly launch defaults to the personal org)
Name:         writebook-ruby-app                                         (derived from your directory name)
Region:       Querétaro, Mexico                                         (this is the fastest region for you)
App Machines: shared-cpu-1x, 1GB RAM                                     (most apps need about 1GB of RAM)
Postgres:     <none>                                                     (not requested)
Redis:        Pay-as-you-go Plan: 10 GB Max Data Size, eviction disabled (determined from app source)
Tigris:       private bucket                                             (determined from app source)

? Do you want to tweak these settings before proceeding? No
Created app 'writebook-ruby-app' in organization 'personal'
Admin URL: https://fly.io/apps/writebook-ruby-app
Hostname: writebook-ruby-app.fly.dev
Set secrets on writebook-ruby-app: SECRET_KEY_BASE

Your database writebook-ruby-app-redis is ready. Apps in the personal org can connect to Redis at redis://default:xxx@fly-writebook-ruby-app-redis.upstash.io:6379

If you have redis-cli installed, use fly redis connect to get a Redis console.

Your database is billed at $0.20 per 100K commands. If you're using Sidekiq or BullMQ, which poll Redis frequently, consider switching to a fixed-price plan. See https://fly.io/docs/reference/redis/#pricing

Redis database writebook-ruby-app-redis is set on writebook-ruby-app as the REDIS_URL environment variable
Your Tigris project (aged-sound-4611) is ready. See details and next steps with: https://fly.io/docs/reference/tigris/

Setting the following secrets on writebook-ruby-app:
AWS_ACCESS_KEY_ID: tidxxxxxxx
AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev
AWS_REGION: auto
AWS_SECRET_ACCESS_KEY: tsec_xxxxxxxxxxxxx
BUCKET_NAME: aged-sound-4611

/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.5.18) required by your /Users//Documents/GitHub/ONCE_book_publishing/writebook-ruby-app/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.5.18`
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
        from /usr/bin/bundle:23:in `<main>'
Error: Failed to install bundle, exiting: exit status 1

See https://fly.io/docs/rails/getting-started/existing/#common-initial-deployment-issues
for suggestions on how to resolve common deployment issues.

the error requires me to download ruby so i did with brew install ruby but the error persisted after. Even though the command brew install ruby executed properly

@laptop writebook-ruby-app % bundle update --bundler
You must use Bundler 2 or greater with this lockfile.
@laptop writebook-ruby-app % gem install bundler:2.5.18
ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.