
When I decided to build a personal Git hosting server, I had a choice: self-host GitLab or build something lighter. I chose the latter, and here's why Rails made it ridiculously fast to build Gisia – a lightweight alternative that proves Rails is still the king for modern web applications.
Why Not GitLab? Why Build Gisia Instead?
GitLab is incredible, but it's also heavy. Here's the reality:
- Resource hungry: Requires Vue.js, Elasticsearch, and more. Just getting it running locally takes significant setup.
- Bloated for small teams: If you just need Git hosting + basic CI/CD, you're carrying around features you'll never use.
- Overkill for personal/small-team use: The complexity overhead isn't worth it when you just want a focused, maintainable codebase.
Gisia takes inspiration from GitLab's feature set but strips away the unnecessary complexity. It's built for:
- Personal Git hosting servers
- Small teams that need CI/CD without the overhead
- Developers who want to understand and customize their Git platform
- Anyone tired of managing ten different services just for version control
The result? A single Rails app with PostgreSQL, minimal external dependencies, and a codebase you can actually understand and modify.
The Frontend Problem: Why I Almost Chose a JavaScript Framework
When I started Gisia, I faced a common dilemma: build a SPA with React/Vue, or keep it simple?
I chose Rails + Hotwired, and it saved me weeks of development time.
The Problem with Heavy Frontend Frameworks
Most modern Git hosting UIs involve:
- Merge request creation and editing
- Real-time status updates for CI/CD pipelines
- Interactive forms and modals
- Live notifications
Traditionally, this means building a JavaScript SPA with:
- React/Vue/Svelte + separate frontend codebase
- Complex build tools (Webpack, Vite, etc.)
- NPM dependency management and lock files
- Redux/Zustand/Pinia state management
- API contracts to maintain between frontend and backend
With a 20,000+ line codebase, you're managing two codebases instead of one.
Hotwired to the Rescue
Gisia uses Turbo and Stimulus – Rails' secret weapons for modern interactivity without the JavaScript bloat.
Turbo handles navigation and form submissions elegantly:
<%= form_with model: @merge_request, local: true do |f| %>
<%= f.text_field :title %>
<%= f.submit "Create", data: { turbo: true } %>
<% end %>
No API endpoints needed. No JSON parsing. Just server-rendered HTML.
Stimulus handles the few cases where you need client-side interactivity:
// app/javascript/controllers/color_picker_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input", "preview"]
change(event) {
this.previewTarget.style.backgroundColor = event.target.value
this.inputTarget.value = event.target.value
}
}
That's it. A few lines of JavaScript for interactive color picking on label creation. In React? You'd have 50+ lines of hooks and state management.
The result: Gisia's entire JavaScript footprint is tiny. No webpack, no npm dependency hell, just simple, declarative controllers that enhance server-rendered HTML.
Solid Queue: Simplifying Background Jobs for CI/CD
Gisia includes CI/CD pipeline support – running builds, executing jobs, storing artifacts. This traditionally means:
- Sidekiq + Redis setup
- Complex job retry logic
- Deployment headaches
Solid Queue changed everything. It's Rails' new job processing framework, and it's fast.
# app/jobs/ci/build_job.rb
class Ci::BuildJob < ApplicationJob
queue_as :default
def perform(build_id)
build = Ci::Build.find(build_id)
build.execute!
end
end
No Redis. No worker processes to manage. Solid Queue uses your existing PostgreSQL database and processes jobs in the background. For Gisia, this means:
- CI/CD pipelines run smoothly without extra infrastructure
- Job execution is straightforward and debuggable
- Scaling is as simple as adding more Rails processes
Compare this to GitLab's architecture where you need separate runner infrastructure, Redis for job queues, and complex coordination. Gisia handles it all with Rails + PostgreSQL.
Rails Architecture: Built for Speed
Here's how Gisia is structured, and why Rails conventions saved me massive amounts of time:
Multi-Database Setup
# config/database.yml
primary:
database: gisia_development
cache:
database: gisia_cache_development
queue:
database: gisia_queue_development
Rails handles this seamlessly. No custom connection logic needed.
Authentication & Authorization
Devise handles user auth. Custom Pundit-style policies handle permissions:
# app/policies/project_policy.rb
class ProjectPolicy
def update?
user.namespace.projects.include?(project)
end
end
Simple, declarative, and testable.
Business Logic in Models and Concerns
Complex operations live in models and concerns, keeping controllers thin and logic organized. In Gisia, the MergeRequest model handles associations, validations, and query scopes directly. Concerns (like MergeRequests::Status and MergeRequests::MergeStatus) handle cross-cutting logic for state transitions and merge behavior. Instance methods encapsulate specific behavior like calculating commit counts.
This approach keeps business logic close to the data where it belongs. Controllers stay thin (under 15 lines per action), and everything is testable on the model without complex setup or service layer indirection.
Real Examples from Gisia
Interactive Label Management
Creating labels with custom colors uses Stimulus controllers for a clean, interactive experience:
// app/javascript/controllers/color_picker_controller.js
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = ['preview']
updatePreview() {
const color = this.element.querySelector('input[type="text"]').value
if (/^#[0-9A-F]{6}$/i.test(color)) {
this.previewTarget.style.backgroundColor = color
}
}
selectColor(event) {
event.preventDefault()
const color = event.target.dataset.colorValue
const input = this.element.querySelector('input[type="text"]')
input.value = color
this.previewTarget.style.backgroundColor = color
}
}
And the Label model is refreshingly simple:
class Label < ApplicationRecord
belongs_to :namespace
has_many :label_work_items, dependent: :destroy
has_many :work_items, through: :label_work_items
validates :title, presence: true
validates :color, presence: true
validates :namespace_id, presence: true
end
That's all you need. A few lines of Stimulus for color preview, a simple model with validations. No React, no Vue, no build pipeline complexity.
Real-Time Pipeline Updates (Planned)
Gisia's future real-time pipeline updates will leverage Turbo Streams for seamless status updates:
<%= turbo_stream_from @pipeline, :builds %>
<div id="<%= dom_id(@pipeline) %>">
<% @pipeline.builds.each do |build| %>
<%= render build %>
<% end %>
</div>
When a build completes (via Solid Queue jobs), a server-side event will broadcast updates. The browser automatically re-renders. Zero custom JavaScript orchestration needed.
Development Speed: The Real Win
Here's what matters: I built Gisia faster than I could have with any other tech stack.
A simple tech stack removes friction. With Rails, you get everything built-in: database migrations, ORM, testing framework, asset pipeline, background jobs, routing, authentication. You don't need to piece together 10 different libraries or spend days configuring build tools.
Compare this to a JavaScript SPA approach: you'd need to choose a frontend framework, pick a state management solution, configure a build tool (Webpack/Vite), decide on an API structure, set up API authentication, handle CORS, and manage environment variables across frontend and backend. Each decision adds complexity and time.
With Rails, these decisions are already made for you. You can focus on building features instead of configuring tooling. The conventional structure means new developers onboard faster. The opinionated defaults eliminate bikeshedding.
For Gisia specifically, this meant:
- Building merge request logic in days instead of weeks
- Implementing CI/CD pipelines without wrestling with job queues
- Adding interactive features with Stimulus without learning a complex JavaScript framework
- Writing tests that actually catch bugs, not fighting test setup
That's the Rails advantage: simplicity and velocity.
The Result
Gisia is a fully-featured Git hosting platform built in Rails 8 with:
- User authentication and authorization
- Project and namespace management
- Merge requests with code review
- CI/CD pipelines and builds
- Real-time status updates
- Interactive UI without heavy frontend frameworks
The entire codebase is maintainable, understandable, and ready for customization. All possible because Rails + Hotwired eliminated the JavaScript complexity that would have bloated the project.
Why Rails is Still the GOAT
Nowadays, people tend to reach for JavaScript frameworks, microservices, or trendy new languages. But Rails has strengths that remain unmatched:
- For web applications, nothing is faster than Rails. Period.
- For teams, Rails conventions eliminate endless bikeshedding.
- For complexity, Rails handles databases, authentication, background jobs, and real-time features seamlessly.
- For learning, a Rails codebase teaches you more about web development than 10 JavaScript frameworks.
Gisia proves it. I built a GitLab alternative with:
- Minimal JavaScript footprint (no build pipeline overhead)
- Minimal external dependencies
- Clean, understandable code
- Incredible development velocity
If you're building a web app in 2025, don't sleep on Rails. Especially if you're tired of JavaScript complexity.
Try Gisia
If you're interested in personal Git hosting or want to see Rails architecture in action, check out Gisia on GitHub.
Rails isn't dead. It's just better than ever.