whoami
Lucas Jenß
cat /etc/motd
The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.
ls -la ~/languages/
- drwxr-xr-x
- ▶ PHP
- ▶ Ruby
- ▶ Scala
- ▶ C#
- ▶ JavaScript
- ▶ Objective-C
- ▶ Shell Scripting
ls -la ~/toolchain/
- drwxr-xr-x
- ▶ Typo3
- ▶ Akka
- ▶ Capistrano
- ▶ Git
- ▶ MAMP
- ▶ Adobe Illustrator
- ▶ NSTrackingArea (Cocoa)
uname -a
- drwxr-xr-x
- ▶ Mac OS X
- ▶ Unix
Migrating a Ruby Sinatra App From Thin to Puma
Thin served many small Sinatra applications well, particularly when the deployment was simple and traffic was modest. Its event-driven model made it easy to start an app with a short command, and many older Ruby projects still contain Thin-specific launch scripts, configuration files, or process manager settings.
Puma is now a common replacement for Ruby web applications because it supports native threads, clustered worker processes, graceful restarts, and current Rack conventions. Moving from Thin is usually straightforward, but the change can expose assumptions about concurrency, environment variables, logging, and how the application is stopped.
The safest migration treats Puma as a runtime change rather than a one-line Gemfile edit. The application needs a tested Rack entry point, a clear server configuration, and a deployment command that matches the hosting platform. Local development should use the same server family as production wherever practical.
For an Australian project, deployment details can matter as much as Ruby code. A Sydney or Melbourne VPS may have different monitoring and backup requirements from a platform service, while an application handling customer information must be reviewed against the Privacy Act 1988 and the Notifiable Data Breaches scheme. A careful server migration is a useful time to check both technical and operational assumptions.
Audit The Existing Thin Setup
Start by locating every place where Thin is mentioned. Inspect the Gemfile, config.ru, shell scripts, Procfile, Docker files, Capistrano tasks, systemd units, and hosting-provider settings. A project may appear to use bundle exec thin start, while a production service actually invokes a custom Ruby script or starts several Thin instances behind nginx.
Record the current port, bind address, environment, PID file, log paths, worker count, and shutdown behaviour. Also identify middleware that depends on process state. In-memory sessions, class-level caches, background jobs started during boot, and singleton objects can behave differently once several Puma threads or workers share the application.
The Rack entry point should be as portable as possible. A typical Sinatra application can use a small config.ru file:
require './app'
run Sinatra::Application
If the project uses a modular Sinatra class, the final line may instead be run MyApp. Keeping this file independent from Thin makes it possible to run the app with Puma, Rack’s command-line tooling, or a platform-specific launcher.
Update Dependencies And Startup Commands
Replace the Thin dependency with Puma in the appropriate Bundler group. For example:
group :production do
gem 'puma'
end
Some teams keep Puma outside the production group so developers use the same server locally. The right choice depends on the deployment process, but the lockfile must be regenerated and committed. Run the application’s test suite after updating Bundler, because a new Rack or Puma version may reveal compatibility issues in older Sinatra code.
The simplest local command is:
bundle exec puma -p 4567 -e development
When a config.ru file is present, Puma can load it directly:
bundle exec puma -C config/puma.rb
A useful configuration file might contain:
environment ENV.fetch('RACK_ENV', 'development')
threads 2, 5
port ENV.fetch('PORT', 4567)
workers ENV.fetch('WEB_CONCURRENCY', 1)
preload_app!
Do not copy these values blindly into production. Two to five threads per worker is only a starting point, and preload_app! should be used after checking that initialisation code is safe to share through forked workers. Remove Thin-specific flags such as --servers, --onebyone, and Thin PID options rather than trying to translate them literally.
Choose A Puma Concurrency Model
Puma can run in single-process threaded mode or in clustered mode with multiple worker processes. Threads are efficient for an I/O-heavy Sinatra app that spends time waiting for PostgreSQL, Redis, HTTP APIs, or file operations. Workers provide process isolation and can make better use of multiple CPU cores, though each worker increases memory consumption.
A first production configuration might be:
workers ENV.fetch('WEB_CONCURRENCY', 2)
threads ENV.fetch('MIN_THREADS', 2),
ENV.fetch('MAX_THREADS', 5)
preload_app!
Measure memory before increasing the worker count. A small Australian retail or booking service hosted on a modest Sydney instance can run out of RAM quickly if every worker loads a large framework, image-processing library, or cache. Cloud bills may also rise without improving response times. Benchmark representative requests rather than relying on a generic concurrency recommendation.
Thread safety deserves a deliberate review. Avoid mutable global state, reuse database connections correctly, and configure connection pools to cover the maximum number of application threads. If Puma allows five threads per process but ActiveRecord has a pool of two connections, requests will queue unexpectedly. Similar limits may exist in Redis clients, API SDKs, and file-based stores.
Replace Process Management And Signals
Thin often runs under an init script, Supervisor, Foreman, or a hosting service. Puma can use the same outer process manager, but the command, PID handling, and signal semantics need checking. A systemd service commonly starts something similar to:
ExecStart=/var/www/myapp/shared/bundle/ruby/3.2.0/bin/puma \
-C /var/www/myapp/current/config/puma.rb
The exact Ruby and release paths depend on the deployment system. Ensure systemd, Docker, or Capistrano sends signals to Puma itself instead of to a wrapper shell that fails to forward them. Puma responds to signals for stopping, restarting, and phased worker replacement, so incorrect PID management can cause dropped requests or overlapping releases.
A reverse proxy such as nginx should continue forwarding traffic to Puma’s listening socket or port. Keep the proxy responsible for TLS, static files, request size limits, and compression where appropriate. Puma should generally bind to 127.0.0.1 or a Unix socket when the proxy is on the same host, rather than exposing the application port publicly.
During a Melbourne evening release or a Sydney morning maintenance window, watch the logs while performing a graceful restart. Confirm that existing requests finish, the new code accepts traffic, and health checks recover. Record timestamps in UTC or include the local zone explicitly; Australia’s daylight-saving differences between Sydney and Brisbane can otherwise make incident timelines confusing.
Compare The Thin And Puma Configuration
The migration becomes easier to review when the old and new responsibilities are separated. Thin’s server flags do not map one-for-one to Puma settings, and some concerns belong to the proxy or process manager rather than the Ruby server.
| Concern | Thin setup | Puma equivalent | Verification |
|---|---|---|---|
| Application entry point | Thin loads Rack app | Puma loads config.ru |
Request returns successfully |
| Port and bind address | -p, -a flags |
port, bind settings |
Proxy reaches the listener |
| Concurrency | Thin processes or event loop | Threads and optional workers | Load test remains stable |
| Environment | -e production |
environment or RACK_ENV |
Correct credentials and config load |
| PID management | Thin PID option | External service manager or Puma PID | Signals reach the right process |
| Logging | Thin log options | stdout, stderr, or configured files | Logs are collected and rotated |
| Shutdown | Thin stop command | Puma signals and service manager | Existing requests drain cleanly |
Use this comparison as a migration checklist, not as a promise that every Thin option has a direct replacement. For example, daemonising Puma inside a container is usually undesirable because the container runtime should supervise a foreground process. Likewise, placing application logs in a local file may conflict with a platform that expects stdout and stderr.
The application’s health endpoint should be tested separately from the home page. A lightweight route that checks essential dependencies can reveal whether Puma has started but the database pool or environment configuration is broken. Avoid making health checks perform expensive work, since orchestration systems may call them frequently.
Test Deployment Behaviour And Application Assets
Run automated tests under Puma or through the same Rack loading path used in production. Then exercise concurrent requests with a small load tool and inspect for intermittent failures. Pay particular attention to session handling, streaming responses, uploads, timeouts, and code that writes to temporary files.
Static assets are another useful audit point. Puma can serve them in simple deployments, but nginx or a CDN is usually a better choice as traffic grows. Smaller SVGs reduce transfer time for mobile users on a train commute or a regional NBN connection. The project’s existing front-end notes on optimizing SVG paths provide a practical companion to improving server-side delivery.
Check cache headers, precompressed assets, and the location of generated files. Multiple Puma workers should not compete to rewrite the same asset or local cache without coordination. If uploads or generated reports are stored on disk, verify that the deployment host provides persistent storage and that backups meet the project’s retention requirements.
Documentation should cover the new command, required environment variables, restart procedure, and rollback path. A developer joining a Brisbane or Perth-based team should be able to run the service without rediscovering production assumptions. Clear technical documentation habits are transferable across languages; even resources about custom Javadoc generation illustrate the value of documenting repeatable tooling rather than relying on personal memory.
Roll Out Puma With A Measured Cutover
Deploy Puma first in a staging environment that resembles production. Confirm Ruby and Bundler versions, database connectivity, reverse-proxy rules, worker counts, logging, metrics, and shutdown behaviour. If the production service uses a Unix socket, test that socket in staging instead of validating only a localhost TCP port.
For the cutover, keep the previous release available and define a rollback command before changing traffic. Start Puma, verify its health endpoint, then switch the proxy or service target. Watch latency, error rates, memory, database connections, and queue times for at least one normal traffic cycle. A gradual release is preferable when the application serves customers across several Australian time zones.
Security checks belong in the same runbook. Restrict the Puma listener, keep secrets outside the repository, apply TLS at the proxy, and review access logs for personal information. If the app stores names, addresses, payment-related details, or booking records, align retention, breach response, and access controls with Australian privacy obligations rather than treating the server swap as purely operational.
Once the service has remained stable, remove Thin from deployment automation and update support notes. Keep the old configuration in version control for historical reference, but avoid leaving an inactive Thin unit enabled on the host. The finished migration should make starting, monitoring, restarting, and reverting the Sinatra application predictable.
Move the change through staging, run a controlled production cutover, and record the Puma settings that proved stable. A small, tested configuration is a stronger foundation than simply increasing workers or threads until errors disappear. With the runtime, process manager, proxy, and application state reviewed together, the Sinatra app can gain Puma’s concurrency and deployment features without turning a server replacement into an avoidable outage.
cat ~/interests.json
| Key | Value |
|---|---|
| editor | Terminal-first workflow |
| os | Mac OS X / Unix |
| vcs | Git, distributed version control |
| deploy | Capistrano, cron automation |
| graphics | SVG, Adobe Illustrator troubleshooting |
| networking | IP validation, SSH, VPN |
git log --oneline --reverse
Solving SVG import issues in Adobe Illustrator CS6 and CC
When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.
Solving NDK build issues on OS X
Troubleshooting native development kit compilation problems on Mac OS X.
Programmatically adding PHP generated TypoScript to the backend configuration
Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.
ArgumentError: Could not parse PKey: no start line
Debugging an SSH key parsing error encountered during deployment.
Validating IP-Addresses in PHP
Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.
Cocoa: Using NSTrackingArea
A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.
cat ~/contact.txt