A failed connect or tick now closes the connection and reconnects after one minute, re-running initialization so a dropped table or row is recreated. Before, a failing tick logged every interval forever and connect retries logged every 10 seconds. Connection URLs that fail to parse no longer appear in logs, since their error repeats the password. An unknown adapter or a missing required config key now stops keepalive at startup instead of silently idling one service.
6.8 KiB
keepalive
Pluggable Go daemon that periodically touches external services to prevent idle shutdowns, pauses, or cold starts.
The current adapters perform cheap datastore writes for Redis Cloud, Valkey, Aiven, Neon, Supabase, MongoDB Atlas, Couchbase Capella, and similar hosted services.
Successor to the *-keepalive family: one binary, one image, five datastore adapters. Valkey and other Redis-compatible stores use the redis adapter.
Configuration
By default, keepalive reads the first config file it finds: config.yml, config.yaml, /config.yml, then /config.yaml. One deployment can keep any number of services alive.
# Default interval for every service.
interval: 1m
counter_key: counter
services:
- adapter: redis
config:
url: redis://default@redis-a.example.com:6379
namespace: keepalive
# Valkey, Dragonfly, KeyDB and other Redis-compatible stores use the redis
# adapter with a redis:// or rediss:// (TLS) URL.
- name: valkey-a
adapter: redis
# One service can override the global interval and counter key.
interval: 30s
counter_key: valkey-counter
config:
url: rediss://default@valkey-a.example.com:6379
namespace: keepalive
- adapter: postgresql
config:
url: postgresql://user:pass@postgres-a.example.com:5432/keepalive?sslmode=require
- adapter: mysql
config:
dsn: user:pass@tcp(mysql-a.example.com:3306)/keepalive
- adapter: mongodb
config:
uri: mongodb+srv://user:pass@mongo-a.example.com
database: keepalive
collection: counter
- adapter: couchbase
config:
connection_string: couchbases://couchbase-a.example.com
username: user
password: pass
bucket_name: keepalive
scope_name: _default
collection_name: _default
name is optional. When omitted, keepalive generates a name from adapter and the connection host, such as redis-redis-a-example-com. Duplicate generated names get suffixes like redis-redis-a-example-com-2.
interval at the root sets the default schedule for every service and defaults to 1m.
interval inside a service overrides that default only for that service.
In the example above, every service runs every 1m except valkey-a, which runs every 30s.
Interval values use Go duration syntax, for example 30s, 5m, 1h, 1h30m, or 1.5h. Plain integers are treated as seconds, so 90 means 90s.
counter_key at the root sets the default counter key for every service and defaults to counter.
counter_key inside a service overrides that default only for that service.
In the example above, every service writes counter except valkey-a, which writes keepalive:valkey-counter.
Supported adapters
adapter |
Driver | config keys |
|---|---|---|
redis |
github.com/redis/go-redis/v9 |
url (redis:// or rediss://), optional namespace |
postgresql |
github.com/lib/pq |
url |
mysql |
github.com/go-sql-driver/mysql |
dsn |
mongodb |
go.mongodb.org/mongo-driver/v2 |
uri, database, collection |
couchbase |
github.com/couchbase/gocb/v2 |
connection_string, username, password, bucket_name, scope_name, collection_name, optional ready_timeout, optional bucket_ram_quota_mb |
Quick start (Compose)
cp config.example.yml config.yml
docker compose up -d --build
compose.yml also deploys on Coolify with the Docker Compose build pack (compose file /compose.yml). Coolify turns the ./config.yml bind mount into an editable file storage; paste your config there. keepalive is a background worker with no port, so leave the service without a domain. Keep the real config.yml out of git, since it holds datastore credentials.
Quick start (Docker)
docker build -t keepalive:local .
docker run -d --name keepalive --restart unless-stopped \
-v "$PWD/config.yml:/config.yml:ro" \
keepalive:local
If you prefer to mount the config into a working directory instead of the container root, set the container working directory and mount the file there:
docker run -d --name keepalive --restart unless-stopped \
--workdir /workspace \
-v "$PWD/config.yml:/workspace/config.yml:ro" \
keepalive:local
Quick start (local)
git clone https://github.com/tiennm99/keepalive
cd keepalive
cp config.example.yml config.yml
go run .
How it works
On startup each adapter initializes the minimum resource it owns, then every tick performs the cheapest write that proves the cluster is alive. counter_key selects the key/doc ID and defaults to counter.
- Redis (also Valkey, Dragonfly, KeyDB, Garnet, Upstash) — initialize with
SETNX key 0, thenINCR key. Whennamespaceis empty, the key iscounter; whennamespace: keepalive, the key iskeepalive:counter. - PostgreSQL —
CREATE TABLE IF NOT EXISTS keepalive, seedkey, thenUPDATE ... RETURNING - MySQL —
CREATE TABLE IF NOT EXISTS keepalive, seedkey, thenUPDATE+SELECT - MongoDB — upsert
{_id: key, count: 0}on connect, thenFindOneAndUpdate({_id: key}, {$inc: {count: 1}}, upsert) - Couchbase — optionally create the bucket when
bucket_ram_quota_mbis set, create configured scope/collection when missing, insertkey = 0if missing, then an atomic binaryINCREMENT key
Each configured service starts independently. When a connect or a tick fails, the service logs the error, closes its connection, and reconnects after 1 minute, which also re-runs initialization (for example, recreating a dropped table). Other services in the same deployment keep running. An unknown adapter or a missing required config key stops keepalive at startup.
For hosted Couchbase/Capella clusters, ready_timeout defaults to 30s. If Couchbase reports CONNECTION_ERROR, check the connection string, bucket name, database user permissions, and Capella allowed IP/network access.
Adding a new adapter
- Create
adapter/<name>.go. - Implement the
Adapterinterface inadapter/adapter.go(Connect,Increment,Close). - Register the factory in
init():func init() { Registry["<name>"] = func(cfg Config) (Adapter, error) { return &myAdapter{}, nil } } - Add an
import _ "your driver"if needed, and the adapter config keys toconfig.example.ymland the table above.
Migrated from
This repo replaces six single-datastore repos (redis-keepalive, valkey-keepalive,
postgresql-keepalive, mysql-keepalive, mongodb-keepalive, couchbase-keepalive).
Their full histories were absorbed into this repository — browse earlier commits to find
each implementation under its own subfolder (redis/, valkey/, postgresql/, mysql/,
mongodb/, couchbase/).
License
Apache-2.0 — see LICENSE.