Rails on Kubernetes

Stack does not force you to rewrite applications in a specific language. As long as you can produce a container image that exposes an HTTP port, the operator can deploy it. Here is a concrete example using Ruby on Rails.

Package the Rails app

Create a multi-stage Dockerfile that installs your gems, builds assets, and ships a slim runtime image:

# syntax=docker/dockerfile:1.5
FROM ruby:3.3 AS build
WORKDIR /app

# Install system packages and gems
RUN apt-get update && apt-get install -y build-essential nodejs yarn
COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test

# Copy source and precompile assets
COPY . .
RUN RAILS_ENV=production bundle exec rake assets:precompile

FROM ruby:3.3-slim
WORKDIR /app
ENV RAILS_ENV=production \
    PORT=3000

RUN apt-get update && apt-get install -y libvips && rm -rf /var/lib/apt/lists/*
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /app /app
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Build and push the image:

docker build -t ghcr.io/acme/rails-app:latest .
docker push ghcr.io/acme/rails-app:latest

Describe the app with Stack

Point a StackApp manifest at your image and port. You can start with static JWT authentication and enable OIDC later by adding hostname-url.

# rails-stack-app.yaml
apiVersion: stack-cli.dev/v1
kind: StackApp
metadata:
  name: rails-app
  namespace: rails-demo
spec:
  components:
    auth: {}
  services:
    web:
      image: ghcr.io/acme/rails-app:latest
      port: 3000

Deploy it:

stack deploy --manifest rails-stack-app.yaml

Stack will ensure the namespace exists, provision a CloudNativePG database cluster, inject credentials into rails-app, and run any database migrations via the migration container specified in future versions of the manifest.

Expose it to the world

Run a quick tunnel while testing:

Add `components.cloudflare: {}` to `rails-stack-app.yaml` and re-run `stack deploy` to start a quick tunnel.
stack status --manifest rails-stack-app.yaml

When you are ready for a permanent hostname, pass --token with a Cloudflare tunnel credential and update components.auth.hostname-url inside the manifest so Keycloak and OAuth2 Proxy enforce proper redirects.