Skip to content

Deploying to Scalingo ​

Trek apps deploy to Scalingo with the dedicated trek-buildpack, a single buildpack that replaces the usual apt + ruby buildpack pair and knows how a Trek app is built.

What the buildpack does ​

  • installs the APT packages listed in Aptfile (image processing libraries, jemalloc), skipping apt-get update and package resolution entirely when the Aptfile is unchanged
  • installs Ruby with rv — precompiled, in seconds — using the version pinned in .ruby-version
  • installs Node.js pinned by .node-version (generated by rails new), and the exact Yarn release pinned by package.json's packageManager field — no unpinned defaults
  • runs yarn install exactly once, with a persistent cache, before assets:precompile
  • slims the shipped image: node_modules, Yarn caches and Node.js itself are dropped after the build, roughly halving the image size of a typical Trek app — faster deploys, restarts and scaling

Compared with the stock buildpacks, deploys typically go from around 3 minutes to about 1 minute 30 with a warm cache.

Set up the app ​

Generate the Scalingo files (an Aptfile and a Procfile with the web process and post-deploy migrations). The generator also comments out the kamal gem rails new adds, which drops its SSH dependencies from the bundle:

sh
bin/rails g trek:scalingo

Then configure the Scalingo app's environment, pinning the buildpack to a release through its archive:

sh
scalingo --app <app> env-set BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack/archive/refs/tags/v0.1.4.tar.gz
scalingo --app <app> env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'
scalingo --app <app> env-set RAILS_MASTER_KEY=$(cat config/master.key) > /dev/null

env-set prints every value it sets, so without > /dev/null the master key ends up in the terminal's scrollback and in any log or transcript of the session. Do the same for every secret below.

Pin through the archive, not a #tag suffix: Scalingo reads #ref on BUILDPACK_URL as a branch, so …/trek-buildpack#v0.1.4 asks for a branch named v0.1.4, which does not exist, and the build fails with fail to download and extract the custom buildpack: Invalid status code: 404 Not Found. The .tar.gz form above is downloaded as is and stays pinned to the release.

Make sure config/database.yml reads DATABASE_URL in production — the section rails new generates targets a local socket with a password variable instead:

yml
production:
  primary: &primary_production
    <<: *default
    url: <%= ENV["DATABASE_URL"] %>

Provision a PostgreSQL addon (scalingo --app <app> addons-add postgresql <plan>), push, and the app boots with RAILS_ENV, RAILS_LOG_TO_STDOUT and RAILS_SERVE_STATIC_FILES already set by the buildpack.

One database ​

The addon is a single database, but the production section rails new generates also declares a cache database for Solid Cache (config/cache.yml says database: cache, its schema is db/cache_schema.rb). The postdeploy db:migrate never loads that schema, so the cache table is never created. Keep the cache in the app's database:

  • drop the cache entry from the production section of config/database.yml, and the database: cache line from config/cache.yml
  • create the table with a regular migration (bin/rails g migration CreateSolidCacheEntries), copying create_table "solid_cache_entries" and its indexes from db/cache_schema.rb
  • delete db/cache_schema.rb

The same goes for any other extra database in that section (queue, cable).

Environment variables ​

Besides the three above, the code Trek generates reads:

VariableUsed for
DEFAULT_HOSTNAMEThe app's host, for URLs built outside a request, including links in emails (config/initializers/host.rb)
MAILER_FROMThe sender of the app's emails (ApplicationMailer)
S3_BUCKET, S3_REGION, S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYThe uploads' bucket (config/initializers/shrine.rb). Required in production: the app doesn't boot without them
S3_BASE_URLThe public URL of the bucket's files, e.g. https://<bucket>.<region>.your-objectstorage.com. Required in production too
APPSIGNAL_PUSH_API_KEYTurns AppSignal on, unless the key is in the credentials (config/appsignal.rb)
APPSIGNAL_APP_ENVThe AppSignal environment, e.g. qa. The gem reads this name, not APPSIGNAL_ENV
RAILS_MAX_THREADS, GOOD_JOB_MAX_THREADSPuma's threads and the jobs' threads, which also size the database pool (config/database.yml)

Jobs run inside the web container (config.good_job.execution_mode = :async), so the generated Procfile has no worker to scale.

env-set doesn't restart the app: running containers keep their old values until scalingo --app <app> restart.

Domains ​

Add each domain to the app, then point it at the app with a CNAME:

sh
scalingo --app <app> domains-add <domain>
# DNS: <domain>  CNAME  <app>.osc-fr1.scalingo.io.

Scalingo issues the Let's Encrypt certificate once the CNAME resolves; until then the domain shows "Pending DNS".

Continuous deployment ​

Deploy from GitLab CI rather than linking the Scalingo app to the repository: the deploy then only runs once the pipeline's tests, lint and security jobs have passed. The generated .gitlab-ci.yml has a deploy stage, with commented-out jobs; replace them with one job per Scalingo app:

yaml
qa:
  <<: *deploy_definition
  only:
    - main
  environment:
    name: qa
    url: https://<domain>
  script:
    - curl -O https://cli-dl.scalingo.com/install && bash install
    - scalingo login --api-token $SCALINGO_API_TOKEN
    - tar --exclude=tmp --exclude=log --exclude=spec --exclude=.git --transform='s,^,master/,' -cvzf tmp/main.tar.gz .
    - scalingo --app <app> deploy tmp/main.tar.gz $CI_COMMIT_SHORT_SHA

SCALINGO_API_TOKEN is a Scalingo API token, set as a masked CI/CD variable of the project or its group. A production job on the production branch follows the same pattern.

Before going live, check what Trek expects from the hosting: backups, a shared cache store, the scheduled purges (see Security and compliance).

Options ​

VariableEffect
TREK_BUILDPACK_RUNTIME_NODE=1Ship Node.js in the image (only if the app runs JavaScript at runtime, e.g. execjs)
TREK_BUILDPACK_PRUNE_SOURCEMAPS=1Also remove .map files from the compiled assets

Migrating an existing app ​

Apps deployed with the stock .buildpacks (apt + ruby) migrate in three steps:

  1. delete .buildpacks (re-running bin/rails g trek:scalingo does it) and check that .node-version exists — apps generated with rails new -j esbuild have it
  2. set BUILDPACK_URL as above, and map DATABASE_URL if the app doesn't read it yet
  3. deploy — the first build fills the caches, the next ones benefit from them

The buildpack's README details the cache layout and the Aptfile syntax.

Released under the MIT License.