Wurk β‘
Wurk, wurk. πͺ Ready to work. Zug zug.
A 100% drop-in replacement for Sidekiq + Sidekiq Pro + Sidekiq Enterprise. Free forever. Faster.
Wurk is wire-compatible with Sidekiq β same Redis keys, same job JSON, same Ruby DSL. Swap one line in your Gemfile and your existing jobs, batches, limiters, cron entries, and live Redis data keep working untouched. The Pro and Enterprise feature sets ship in the same free gem, with no license check and no tiers.
Install
# Gemfile
gem "wurk"
# ...or drop in over an existing Sidekiq stack β delete these, add one line:
- gem "sidekiq"
- gem "sidekiq-pro", source: "https://gems.contribsys.com/"
- gem "sidekiq-ent", source: "https://enterprise.contribsys.com/"
+ gem "wurk"
bundle install && restart. That's it β Sidekiq::Worker, Sidekiq::Batch, Sidekiq::Limiter, Sidekiq.configure_server, and friends all resolve to Wurk.
Feature matrix
Everything below is in the one free gem. The "Sidekiq tier" column is only there to show what you'd otherwise pay for.
| Area | What you get | Sidekiq tier |
|---|---|---|
| Runtime | Fork-based real parallelism, reliable BLMOVE fetch, PID supervision, rolling restarts, graceful drain, scheduled/retry pollers |
OSS + Pro |
| Batches | Sidekiq::Batch with on(:success/:complete/:death) callbacks, nested batches, progress |
Pro |
| Limiters | Concurrent, bucket, window, leaky, and points rate limiters via Sidekiq::Limiter |
Enterprise |
| Periodic | Cron/periodic jobs, leader-elected so each tick fires exactly once across the cluster | Enterprise |
| Encryption | Transparent AES-256-GCM job-argument encryption with zero-downtime key rotation | Enterprise |
| Dashboard | Mountable Rails engine, precompiled SolidJS SPA (no Node needed), live SSE, charts, host-app auth hook | OSS + Pro/Ent |
Plus Wurk extras: a worker topology DSL, a Kubernetes liveness/readiness listener, and opt-in AI dashboard panes (anomaly detection, NL queries, backlog forecasting).
Documentation
- Website Β· Wiki / full docs β the pitch, install, and the complete guide.
- API reference (YARD) β generated docs for the public classes (
Wurk::Worker,Wurk::Client,Wurk::Configuration,Wurk::Batch,Wurk::Limiter,Wurk::Unique, and theSidekiq::*aliases). Machine-readable map for AI agents: llms.txt. - Getting started & architecture β how the swarm, manager, fetcher, and processor fit together.
- Starting the worker β Rails auto-start, the
wurk/wurkswarmrunners, and running standalone without Rails. - Active Job adapter β run
ActiveJob/deliver_lateron Wurk withqueue_adapter = :wurk. - Migrating from Sidekiq β the one-line swap and what to expect.
- API reference (parity specs): Sidekiq OSS Β· Pro Β· Enterprise β the authoritative surface Wurk matches exactly.
- Securing the dashboard Β· Metrics history
- Compatibility & legal basis β clean-room implementation: Wurk copies the API, not the code (Google v. Oracle).
- Live demo: wurk.demo.developerz.ai
Requirements
| Component | Minimum |
|---|---|
| Ruby | >= 3.2.0 |
| Redis | >= 7.0.0 |
JRuby, TruffleRuby, and Windows fall back to threads-only mode (no fork) β behaviorally equivalent to stock Sidekiq.
Running the workers
Under Rails the engine auto-starts the swarm on boot β a plain rails server already forks workers and fetches. Set WURK_DISABLED=1 on any process that shouldn't (e.g. the web tier when you run workers on their own dyno).
One exception β preforking web servers. A clustered Puma, Unicorn, or Passenger forks its own web workers, so Wurk refuses to also fork the swarm there β it would multiply the swarm by the web-worker count, or entangle its supervisor with the server's own fork/signal handling β and logs how to proceed. Either run the swarm as its own process (recommended):
bundle exec wurkswarm # forked swarm, real parallelism
β¦or run it inside the web process as threads only, no fork (like Sidekiq embedded):
# config/application.rb (here, not an initializer β server mode is decided before initializers load)
config.wurk. = true
Single-mode Puma (the rails server default) isn't preforking, so auto-start is unaffected. Full details, flags, and the standalone runners: Starting the worker.
The dashboard
Mount the engine wherever you like:
# config/routes.rb
mount Wurk::Engine => "/wurk"
The precompiled SPA ships inside the gem, so consumers never run Node. Gate it behind your app's auth with one line β see Securing the dashboard for Devise/Warden/Sorcery recipes:
Wurk::Web.use(Rack::Auth::Basic, "Wurk") { |user, pass| user == ENV["WURK_USER"] && pass == ENV["WURK_PASS"] }
Ship a viewer-only board (e.g. a public demo) with no auth code at all by setting WURK_WEB_READ_ONLY=1 β every mutating request returns 403 and the SPA hides destructive actions.
Security notes
Wurk::Web.useand theauthorizationhook gate the dashboard's routes and JSON API β every controller under the engine mount goes through them./wurk-assets/*(the precompiled SPA's JS/CSS/font bundle) is served unauthenticated, by design. It's inserted into the host app's own middleware stack ahead of the engine's routes, so it never reachesWurk::Web.use/authorization. This is safe because the bundle carries no data β no job payloads, no Redis reads, nothing per-user β it's a static shell, same trust model as any Rails app'spublic/assets. Everything data-bearing (stats, queues, jobs) is served by the JSON API, which is gated. If you need to hide even the existence of the bundle (e.g. compliance requires the mount path itself stay secret), put a reverse-proxy rule in front of/wurk-assetsrather than relying on the engine.- Redis being unreachable surfaces to the SPA as a structured
503 {"error": "redis_unavailable"}(JSON endpoints) or an SSEerrorevent (the live stream), never a raw 500 β the client can branch on it instead of parsing an HTML error page.
Encryption
A drop-in for Sidekiq::Enterprise::Crypto. It encrypts the last positional argument of a job with AES-256-GCM β the client middleware seals it on push, the server middleware opens it before perform. Earlier args stay plaintext so you can still triage on user_id.
# config/initializers/wurk.rb β point at any key source (file, ENV, KMS)
Sidekiq::Enterprise::Crypto.enable(active_version: 1) do |version|
File.binread("config/crypto/secret.#{Rails.env}.#{version}.key") # exactly 32 bytes
end
class ChargeCardJob
include Sidekiq::Job
encrypt: true
def perform(user_id, secret_bag) # secret_bag arrives already decrypted
Payments.charge(user_id, secret_bag["pan"], secret_bag["cvv"])
end
end
Keys rotate without downtime β keep every still-in-flight version resolvable so old jobs decrypt, then bump active_version. A job that can't be decrypted (key rotated away, corrupt ciphertext) goes straight to the dead set in under a second rather than crash-looping through 25 retries, with the still-encrypted payload preserved for replay. The dashboard renders encrypted args as "<encrypted>"; cleartext is never written to Redis.
Kubernetes probes
Opt in to a thin HTTP listener for liveness/readiness:
Wurk.configure_server do |config|
config.health_check(port: 7433)
end
| Path | Meaning |
|---|---|
/live |
200 while the Launcher is running; 503 once stop/quiet is called. |
/ready |
200 only when Redis is reachable and the heartbeat fired within ready_window (default 30s); 503 otherwise. |
Knobs: health_check(port:, bind: "0.0.0.0", ready_window: 30). In swarm mode only the first child to start binds the port.
Migrating from Sidekiq
- gem "sidekiq"
- gem "sidekiq-pro", source: "https://gems.contribsys.com/"
- gem "sidekiq-ent", source: "https://enterprise.contribsys.com/"
+ gem "wurk"
bundle install && restart. Wurk reads and writes the same Redis schema, so a rolling deploy can run Sidekiq and Wurk against the same Redis during the cutover. Third-party gems (sidekiq-cron, sidekiq-unique-jobs, sidekiq-scheduler, sidekiq-status, sidekiq-failures, sidekiq-throttled, β¦) are exercised by running their own upstream suites against Wurk in the ecosystem CI job (see test/ecosystem/).
Full walkthrough β config side-by-side, the Redis key/sidekiq_options mapping, known incompatibilities, and a one-page cutover checklist: docs/migrate-from-sidekiq.md.
Contributing
Issues and pull requests are welcome β see CONTRIBUTING.md for the dev setup, test layers, and conventions, and SECURITY.md to report a vulnerability.
License
MIT. See LICENSE.
Wurk is a clean-room reimplementation of the Sidekiq API β it copies the interface (so your jobs run unchanged), not Sidekiq's implementation code. This is the same basis the Supreme Court upheld for Google's reuse of the Java API in Google v. Oracle (2021). "Sidekiq" is a trademark of Contributed Systems, LLC; Wurk is independent and not affiliated with or endorsed by them. Full reasoning: docs/clean-room.md.