Skip to content

Setup Kamal for Vignettes MaMpf production deployment#973

Open
Splines wants to merge 119 commits into
nextfrom
kamal
Open

Setup Kamal for Vignettes MaMpf production deployment#973
Splines wants to merge 119 commits into
nextfrom
kamal

Conversation

@Splines
Copy link
Copy Markdown
Member

@Splines Splines commented Dec 16, 2025

This PR sets up Kamal such that we can deploy MaMpf for Vignettes on https://vignettes.mampf.media/. As reviewers, please read about the basics of Kamal such that terminology is clear here.

I rewrote the production Dockerfile (see Dockerfile-vignettes) and set up a deploy.yml file. It contains all the steps to deploy the Vignettes-MaMpf. This way, it should take like only a few hours to setup a completely new MaMpf server in the future (but with limitations right now, see below).

Deploying

In Kamal, I've configured MaMpf as the main web app. This will experience zero-downtime deploys using kamal proxies. Separately managed are so-called accessories like the database. They are separate Docker containers with their own lifecycle and not affected by kamal deploy. Instead they are managed by kamal accessory.

In the latest version of this PR, I've set local: false in the builder section of deploy.yml and configured our server to also build the image. This way, you don't have to do this on your local machine (which consumes much CPU/RAM). If you still want to, just comment out local: false. It will then build locally (but only if your local architecture matches amd64).

Note that Kamal only takes into account any changes that you've committed. Your commit doesn't have to be published with git push, but at least you must have a local commit. You can later still amend your commit to create a new one without having to spam 1000 Git commits when you try things out. Important: this commit restrictions appears to not apply to the Kamal accessories, there it also works without committing first. So watch out a bit.

To be able to deploy, you need to generate an SSH key for the kamal user. Then, use the following ssh config (mampf.media points to the same server as vignettes.mampf.media)

Host 91.98.178.89
    Hostname mampf.media
    User kamal
    IdentityFile ~/.ssh/kamal
    AddKeysToAgent yes

You also need the .env file with all the secrets. Ask for it on Basecamp.

Limitations / Future ideas

  • We use Mailtrap as SMTP relay for this setup. As we only have the free tier, emails are limited to 150/day (including error emails).
  • Setting up media-forwarding correctly was not possible for me in the short time-frame. In Vignettes, you can still upload images and videos and it works fine. But not manuscripts etc. as for those we would need our /mediaforward to work correctly. I've tried setting up nginx as accessory, but as not all requests are then proxied by it, I could never get the config right for X-Accel-Redirect. See also this discussion without any answers.
  • Static assets (not media like manuscripts or videos, but JS/CSS etc. assets) are served not by nginx, but by the Rails Puma web server itself. This may be a lot slower than serving over a dedicated webserver. However, setting a dedicated webserver up to work well with kamal-proxy proved difficult. See also my comments/links on the string config.public_file_server.enabled in production.rb. This is something for future PRs. It still feels blazing fast from first experiments.
  • We don't use any password manager to store our secrets and pass around an .env file at the moment. Security-wise, this is not the best idea. But a password manager integration is only nice to have, not essential, so let's set this up in a future PR. Note that due to us using the .env file, all kamal command should be preprended by bundle exec dotenv,e .g. bundle exec dotenv kamal deploy instead of kamal deploy. Otherwise you will get missing key errors.
  • It would be nice to trigger automatic builds via GitHub Actions in the future.
  • There is no automatic backup solution for the database and the media mounts set up yet.

New dependencies

  • dotenv and kamal

Off-topic

  • Prometheus exporter (previously not used, but still in there as gem) is now removed since it led to problems. This is probably just some misconfiguration, but I didn't want to tackle these problems in this PR, so I removed the gem and configuration here.
  • A lot of production env variables have changed, so maybe we should wait before merging this PR to calmly go through them again and adapt our old setup for "main MaMpf". Independent of whether this PR is merged or not, we can still deploy Vignettes MaMpf just by using this branch.

⚠ To be done for this PR

  • Export old database dump (probably again if something vignettes-related changed since the last dump.
  • Import database dump.
  • Get ActiveStorage secret key right. Replace by new one or better: find a solution such that this replacement doesn't have to be made in the first place.
  • Get ActiveStorage hardcoded url host right. Replace by new one or better: find a solution such that relative URLs are used instead of absolute ones.
  • Set up the error mail for vignettes, see Basecamp TODOs.
  • I removed Node.js from the final build stage. However, apparently it is needed for CoffeeScript, since it uses ExecJS which in turn needs a working Node.js environment.
  • Setup some better way to share the .env file
  • Setup automatic backups

@Splines Splines requested a review from f-buerckel December 16, 2025 00:42
@Splines Splines self-assigned this Dec 16, 2025
# as both mampf-experimental and mampf-next also run in production mode.
production_name = ENV.fetch("PRODUCTION_NAME", nil)
return unless production_name == "mampf"
return if production_name != "mampf" && production_name != "mampf-vignettes"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For vignettes, the user cleaner should be disabled. Otherwise, study data may be destroyed as a result.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the line I wrote above does exactly that, right? Since production_name == "mampf-vignettes" for the Vignettes, so we early-return in that case. Same for mampf-next and mampf-experimental.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the unless keyword.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the unless is gone. If production_name == "mampf-vignettes" the second condition is violated, so the code continues.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh fail, my bad, you're absolutely right. I will just revert this change I guess.

Comment thread config/environments/production.rb
Copy link
Copy Markdown
Collaborator

@fosterfarrell9 fosterfarrell9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only minor remarks. Of course, the big test will be when we deploy this when production_name is just mampf, not mampf-vignettes.

fosterfarrell9 and others added 2 commits March 13, 2026 19:28
It appears that the time users spend on Vignettes slide was not
correctly transmitted to the backend after the first slide. This PR
fixes this and adds a new Playwright test for the Vignette Export.

- Note that some tests report statistic results off by 1 second, even
though we use the precise Playwright clock. For one issue, I was able to
trace this back to an implementation bug. I've noted it, but we probably
don't want to fix it now to avoid skewing the data. Another
off-by-1-second test error is unexplainable to me. But let's not waste
more energy into this, I think a variance of 1s is quite fine here.
- We couldn't get the test to run correctly in CI, so we disable it for
now. Locally it runs through fine. This is probably due to the option
`PLAYWRIGHT_HTML_OPEN=never` we use for `npx playwright test` in the CI.
We will get back to this later, but right now Müsli has priority.

## Off-topic

- Decreases playwright test timeout to 45s (was 90s beforehand)

---------

Co-authored-by: Splines <info@splines.me>
Co-authored-by: Splines <37160523+Splines@users.noreply.github.com>
Splines and others added 2 commits March 13, 2026 19:39
Don't worry, we weren't much behind in terms of version numbers, but
Pagy made a leap:

> We needed a leap version to unequivocally signaling that it's not just
a major version: it's a complete redesign of the legacy code at all
levels, usage and API included.
> Why 43? Because it's exactly one step beyond "The answer to the
ultimate question of life, the Universe, and everything."

Not necessarily modest, but it still seems promising. And as I'd like to
use infinite scroll for the new dashboard lecture browsing, it's better
to upgrade now than later.

I've essentially followed the [upgrade
guide](https://ddnexus.github.io/pagy/guides/upgrade-guide/). There were
some major redesigns in the API, especially the fact that you don't use
`Pagy.new(...)` anymore, but instead use `include Pagy::Method` in the
`ApplicationController`, and then just call `pagy()` wherever you need
it. Also see [this
section](https://ddnexus.github.io/pagy/toolbox/paginators/#the-pagy-method).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants