5 Commits

Author SHA1 Message Date
482a68637e Fix rate limiting: exempt health/metrics, increase upload limits
- Health, metrics, UI, and API docs are now exempt from rate limiting
- Increased upload rate limits to 200 req/s with burst of 500 for Docker compatibility
2026-01-26 11:04:14 +00:00
61f8a39279 Use self-hosted runner for release builds
16-core runner should be 3-4x faster than GitHub's 2-core runners
2026-01-26 10:39:04 +00:00
835a6f0b14 Speed up release workflow
- Remove duplicate tests (already run on push to main)
- Build only for amd64 (arm64 rarely needed for VPS)
2026-01-26 10:18:11 +00:00
340c49bf12 Fix formatting 2026-01-26 10:14:11 +00:00
c84d13c26e Increase upload rate limits for Docker parallel requests
Docker client sends many parallel requests when pushing layers.
Increased upload rate limiter from 10 req/s to 50 req/s and burst from 20 to 100.
2026-01-26 10:10:45 +00:00
3 changed files with 20 additions and 27 deletions

View File

@@ -9,25 +9,9 @@ env:
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Run tests
run: cargo test --package nora-registry
build:
name: Build & Push
runs-on: ubuntu-latest
needs: test
runs-on: self-hosted
permissions:
contents: read
packages: write
@@ -63,7 +47,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -219,14 +219,22 @@ async fn run_server(config: Config, storage: Storage) {
.merge(registry::pypi_routes())
.layer(rate_limit::upload_rate_limiter());
let app = Router::new()
// Routes WITHOUT rate limiting (health, metrics, UI)
let public_routes = Router::new()
.merge(health::routes())
.merge(metrics::routes())
.merge(ui::routes())
.merge(openapi::routes())
.merge(openapi::routes());
// Routes WITH rate limiting
let rate_limited_routes = Router::new()
.merge(auth_routes)
.merge(registry_routes)
.layer(rate_limit::general_rate_limiter()) // General rate limit for all routes
.layer(rate_limit::general_rate_limiter());
let app = Router::new()
.merge(public_routes)
.merge(rate_limited_routes)
.layer(DefaultBodyLimit::max(100 * 1024 * 1024)) // 100MB default body limit
.layer(middleware::from_fn(request_id::request_id_middleware))
.layer(middleware::from_fn(metrics::metrics_middleware))

View File

@@ -30,8 +30,8 @@ impl Default for RateLimitConfig {
Self {
auth_rps: 1, // 1 req/sec for auth (strict)
auth_burst: 5, // Allow burst of 5
upload_rps: 10, // 10 req/sec for uploads
upload_burst: 20, // Allow burst of 20
upload_rps: 200, // 200 req/sec for uploads (Docker needs high parallelism)
upload_burst: 500, // Allow burst of 500
general_rps: 100, // 100 req/sec general
general_burst: 200, // Allow burst of 200
}
@@ -58,15 +58,16 @@ pub fn auth_rate_limiter() -> tower_governor::GovernorLayer<
/// Create rate limiter layer for upload endpoints
///
/// Default: 10 requests per second, burst of 20
/// Default: 200 requests per second, burst of 500
/// High limits to accommodate Docker client's aggressive parallel layer uploads
pub fn upload_rate_limiter() -> tower_governor::GovernorLayer<
tower_governor::key_extractor::PeerIpKeyExtractor,
governor::middleware::StateInformationMiddleware,
axum::body::Body,
> {
let config = GovernorConfigBuilder::default()
.per_second(10)
.burst_size(20)
.per_second(200)
.burst_size(500)
.use_headers()
.finish()
.unwrap();
@@ -101,7 +102,7 @@ mod tests {
let config = RateLimitConfig::default();
assert_eq!(config.auth_rps, 1);
assert_eq!(config.auth_burst, 5);
assert_eq!(config.upload_rps, 10);
assert_eq!(config.upload_rps, 200);
assert_eq!(config.general_rps, 100);
}