Deploy Background Workers
Long-running processes that just keep running. Push your worker, we keep it alive.
Perfect For
Queue Consumers
Pull jobs off SQS, Redis, RabbitMQ, or any queue and process them as they arrive
Pollers and Sync Loops
Watch an external API, sync data between systems, or reconcile state on a tight loop
Notification Dispatchers
Subscribe to events and fan out emails, push notifications, or webhooks downstream
Realtime Processors
Consume Kafka or Kinesis streams, process events, write results to your database
How It Works
-
1
Write your worker
A normal Node.js, Python, or Go process. Loop forever, await your queue, do the work.
-
2
Push to Tokay
git push tokaywe detect it as a background worker. -
3
It just runs
No URL, no schedule. Your process stays alive in the background, and we give you logs and a way to stop and start it.
-
4
It stays up
If your process crashes, we restart it. If you push a new version, we swap it in cleanly.
When to Pick a Background Worker
The four service types each fit a different shape of work. Here's the quick test:
No URL, no schedule, just keeps running
That's a Background Worker. Queue consumers, pollers, daemons.
Has a public URL people visit in a browser
Use a Web Service.
Runs on a clock
Use a Scheduled job instead. The runtime starts your process on the schedule and exits when it's done.
Receives HTTP requests from another system
Use a Function. You get a webhook URL and request history.
Just Write Your Worker
No framework to learn. Loop, do work, log. We keep the process alive.
Node.js Queue Consumer
// worker.js - Pulls jobs off SQS forever
const { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } = require('@aws-sdk/client-sqs');
const sqs = new SQSClient({ region: 'us-east-1' });
const QueueUrl = process.env.QUEUE_URL;
async function processJob(body) {
console.log('Processing', body.id);
// ... do the work
}
async function run() {
console.log('Worker started');
while (true) {
const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({
QueueUrl,
WaitTimeSeconds: 20,
MaxNumberOfMessages: 10,
}));
for (const m of Messages) {
await processJob(JSON.parse(m.Body));
await sqs.send(new DeleteMessageCommand({ QueueUrl, ReceiptHandle: m.ReceiptHandle }));
}
}
}
run().catch((err) => {
console.error('Worker crashed:', err);
process.exit(1);
});
Python Poller
# poller.py - Sync an external API every 30 seconds, forever
import os, time, requests
API = os.environ['UPSTREAM_URL']
TOKEN = os.environ['UPSTREAM_TOKEN']
def sync_once():
res = requests.get(API, headers={'Authorization': f'Bearer {TOKEN}'})
res.raise_for_status()
for item in res.json():
# ... write to your database
print('synced', item['id'])
def run():
print('Poller started')
while True:
try:
sync_once()
except Exception as e:
print('sync failed:', e)
time.sleep(30)
if __name__ == '__main__':
run()
Go Stream Processor
// main.go - Consume a Kafka topic forever
package main
import (
"context"
"log"
"os"
"github.com/segmentio/kafka-go"
)
func main() {
r := kafka.NewReader(kafka.ReaderConfig{
Brokers: []string{os.Getenv("KAFKA_BROKERS")},
Topic: "events",
GroupID: "tokay-worker",
})
defer r.Close()
log.Println("Worker started")
for {
msg, err := r.ReadMessage(context.Background())
if err != nil {
log.Fatalf("read failed: %v", err)
}
log.Printf("event %s: %s", msg.Key, msg.Value)
// ... process the event
}
}
Push it. We detect it as a background worker, keep it running, and stream you the logs.
What We Handle For You
Stays Alive
If your worker crashes, we restart it. You don't have to write a supervisor or systemd unit.
Live Logs
Stdout and stderr stream straight to the dashboard. Watch your worker do its thing in real time.
Clean Deploys
Push a new version and we swap the running process. No URL flips to coordinate, no requests to drain.
Secrets and Env
Queue URLs, API tokens, DB credentials. Manage them in the dashboard, read them as environment variables.
Run your worker without the babysitting
Push code, we keep it running. That's the whole deal.