feat: add Cloudflare Containers deployment support (#20932)

- Create Container Worker entry point with load balancing (20 instances)
- Add wrangler-container.toml configuration with KV binding for environment variables
- Configure container to use standard-1 instance type with Chromium support
- Implement dynamic environment variable loading from Cloudflare KV

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
DIYgod
2026-01-20 15:29:13 +08:00
committed by GitHub
parent 77b7763cd7
commit 25cd9f1a28
6 changed files with 114 additions and 1 deletions
+2
View File
@@ -40,3 +40,5 @@ package-lock.json
# pnpm-lock.yaml
yarn.lock
yarn-error.log
dist-container
+48
View File
@@ -0,0 +1,48 @@
// Cloudflare Container Worker entry point
// This Worker manages the RSSHub container lifecycle and proxies requests
import { Container } from '@cloudflare/containers';
import type { KVNamespace } from '@cloudflare/workers-types';
const INSTANCE_COUNT = 20;
export class RSSHubContainer extends Container {
defaultPort = 1200;
sleepAfter = '10m';
enableInternet = true;
}
interface Env {
RSSHUB_CONTAINER: DurableObjectNamespace<RSSHubContainer>;
CONFIG: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Load env vars from KV
const envVars: Record<string, string> = {
NODE_ENV: 'production',
};
const keys = await env.CONFIG.list();
await Promise.all(
keys.keys.map(async ({ name }) => {
const value = await env.CONFIG.get(name);
if (value) {
envVars[name] = value;
}
})
);
// Randomly select an instance for load balancing
const instanceIndex = Math.floor(Math.random() * INSTANCE_COUNT);
const container = env.RSSHUB_CONTAINER.getByName(`rsshub-${instanceIndex}`);
// Start container with env vars and wait for port to be ready
await container.startAndWaitForPorts({
startOptions: { envVars },
});
return container.fetch(request);
},
};
+3
View File
@@ -36,6 +36,8 @@
"worker-dev": "npm run worker-build && wrangler dev",
"worker-deploy": "npm run worker-build && wrangler deploy",
"worker-test": "npm run worker-build && vitest run lib/worker.worker.test.ts",
"container-build": "tsdown --config ./tsdown-container.config.ts",
"container-deploy": "npm run container-build && wrangler deploy --config wrangler-container.toml --containers-rollout=immediate",
"dev": "cross-env NODE_ENV=dev NODE_OPTIONS='--max-http-header-size=32768' tsx watch --inspect --clear-screen=false lib/index.ts",
"dev:cache": "cross-env NODE_ENV=production NODE_OPTIONS='--max-http-header-size=32768' tsx watch --clear-screen=false lib/index.ts",
"format": "eslint --cache --fix \"**/*.{ts,tsx,js,yml}\" --concurrency auto && oxfmt .",
@@ -144,6 +146,7 @@
"@babel/preset-env": "7.28.6",
"@babel/preset-typescript": "7.28.5",
"@bbob/types": "4.3.1",
"@cloudflare/containers": "0.1.0",
"@cloudflare/puppeteer": "1.0.4",
"@cloudflare/workers-types": "4.20260118.0",
"@eslint/eslintrc": "3.3.3",
+9 -1
View File
@@ -296,6 +296,9 @@ importers:
'@bbob/types':
specifier: 4.3.1
version: 4.3.1
'@cloudflare/containers':
specifier: 0.1.0
version: 0.1.0
'@cloudflare/puppeteer':
specifier: 1.0.4
version: 1.0.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)
@@ -1104,6 +1107,9 @@ packages:
'@bundled-es-modules/tough-cookie@0.1.6':
resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==}
'@cloudflare/containers@0.1.0':
resolution: {integrity: sha512-nuXnmkpJZzbjYdguRI2hB0sw1QCBMWdNuGDNQwEiJSLebtKRFpBt/d6AStGjp+8wGD2plPbd2U/mQerYF9kzJg==}
'@cloudflare/kv-asset-handler@0.4.2':
resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==}
engines: {node: '>=18.0.0'}
@@ -7553,6 +7559,8 @@ snapshots:
'@types/tough-cookie': 4.0.5
tough-cookie: 4.1.4
'@cloudflare/containers@0.1.0': {}
'@cloudflare/kv-asset-handler@0.4.2': {}
'@cloudflare/puppeteer@1.0.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
@@ -12251,7 +12259,7 @@ snapshots:
debug: 4.4.3
devtools-protocol: 0.0.1367902
typed-query-selector: 2.12.0
ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)
ws: 8.19.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bare-buffer
- bufferutil
+16
View File
@@ -0,0 +1,16 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
entry: ['./lib/container.ts'],
outDir: 'dist-container',
format: 'esm',
minify: true,
clean: true,
platform: 'node',
target: 'esnext',
treeshake: true,
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
external: ['@cloudflare/containers'],
});
+36
View File
@@ -0,0 +1,36 @@
name = "rsshub-container"
main = "dist-container/container.mjs"
compatibility_date = "2026-01-16"
compatibility_flags = ["nodejs_compat"]
[build]
command = "pnpm run container-build"
[observability]
enabled = true
# Container configuration
[[containers]]
class_name = "RSSHubContainer"
image = "./Dockerfile"
max_instances = 20
instance_type = "standard-1"
image_vars = { PUPPETEER_SKIP_DOWNLOAD = "0" }
# Durable Object binding for the container
[[durable_objects.bindings]]
name = "RSSHUB_CONTAINER"
class_name = "RSSHubContainer"
# Required migration for Durable Objects with SQLite
[[migrations]]
tag = "v1"
new_sqlite_classes = ["RSSHubContainer"]
# KV namespace for container environment variables
[[kv_namespaces]]
binding = "CONFIG"
# Example: Set env vars in KV using wrangler CLI:
# wrangler kv:key put --namespace-id=rsshub-container-config "GITHUB_ACCESS_TOKEN" "your-token"
# wrangler kv:key put --namespace-id=rsshub-container-config "ACCESS_KEY" "your-key"