To document this for other snapcrafters that come across issues while snapping Ruby/Rails applications, here are the steps I took to get my Rails stack running in a snap. Thanks again to @Daniel for providing insight about current limitations of users/groups in snaps, and to @zyga-snapd for helping me understand the layouts feature!
Prelude: I require a strictly confined snap, because the app will be deployed on Ubuntu Core devices where snaps with
classic confinement are not available.
Task: Getting the database to work
As explained by @daniel in this thread, services in a snap can only run as root user at the moment. Programs such as Postgres straight-out refuse to start as root. This means that if your Rails app uses a PostgreSQL database, you need to either:
- Build Postgres from source with custom patches that disable hardcoded “am I running as root” checks
- Switch to another database – in my case, SQLite3 is enough for the use case. MySQL has an escape hatch which allows you to run it as root anyway, see @kyrofa’s Nextcloud snap for details.
- Use the Postgresql snaps by commandprompt to manually start and manage Postgres – I didn’t try that yet.
Task: bundling Ruby and gem dependencies
If your app relies on other versions than what’s available in the Ubuntu 16.04 repos (currently 2.3.0), use the
ruby plugin provided by snapcraft. It lets you select any Ruby version published on ruby-lang.org and also provides convenient keywords to specify global gems that should be installed. See
snapcraft help ruby for details.
Here’s the stanza from my snapcraft.yaml that specifies the Rails app as a part:
build-packages: [libsqlite3-dev] # Libraries required during gem native extension builds
stage-packages: [sqlite3] # Bundling sqlite3 drivers
gems: [bundler, rails] # Installing bundler and rails in $SNAP/bin to run bundle and rails commands in scripts
cp -a * $SNAPCRAFT_PART_INSTALL/my-rails-app
bin/bundle install --path vendor/bundle --without development test
rm -rf $SNAPCRAFT_PART_INSTALL/api/tmp $SNAPCRAFT_PART_INSTALL/api/log
override-build section shows how you can move the app to a subdirectory in the final snap. This is useful e. g. if you’re bundling a separate frontend app or other things that might conflict with your Rails app’s files, or if you just want a clean top-level directory. If you don’t care where bundler installs your app’s gem dependencies or if dev/test are installed as well, you can just use the
ruby plugin keyword
use-bundler: true. The last line ensures that
log directories are removed because we are replacing those with bind mounts in the next section. The default .gitignore of a fresh Rails app includes those directories with .gitkeep files, so they are present in a fresh clone of your repository if you don’t untrack them.
Task: Providing Rails with write access to tmp/log directories
Rails apps currently require write access to the directories
log in their application root. However, those are read-only in a snap environment. To work around this, you need to use the (currently-experimental-but-soon-to-be-stable) snapd feature: layouts.
passthrough: # TODO: Remove once in stable snapd
$SNAP/my-rails-app/tmp: # my-rails-app == subdirectory from override-build
bind: $SNAP_DATA/rails_tmp # or any other name that suits you
This creates bind-mounts that are writable by Rails even though the directories are inside the snap’s read-only filesystem.
Task: Running Ruby/Rails commands in snap hooks
To set up your Rails app, run migrations etc., you can use snap hooks. However, hooks are not automatically wrapped by snapd to ensure that the snap’s binaries and libraries are in the PATH (I have opened a PR for snapcraft documentation to explain this for first-time snap creators like me). To ensure that everything bundled by the Ruby plugin is available during hook runs, add the following to your snapcraft.yaml:
# top-level section in snapcraft.yaml
I just copied over all environment exports that snapd created for a command in my snap. So if you’re unsure what to put here, create a dummy app with a simple command, run
snapcraft prime my-dummy-command and inspect the file
snap-project/prime/my-dummy-command.wrapper for guidance.