- Add /api/stats/hottest endpoint for popular images by containers and adoption - Add /api/stats/movers endpoint for week-over-week risers and fallers - Add /api/stats/new-entries endpoint for newly discovered images - Add image_stats_weekly table with automatic weekly snapshots - Add Trends tab to vanilla JS dashboard with interactive charts - Add collector-build.sh and collector-run.sh scripts for local development - Add Next.js example components (HottestImagesChart, MoversChart, NewEntriesCard) - Update telemetry-api.ts with new trending API methods and types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Container Census Next.js Integration
This directory contains ready-to-use components and utilities for integrating Container Census telemetry data into your Next.js application.
Table of Contents
- Overview
- Prerequisites
- Installation
- Configuration
- Usage
- Available Components
- API Reference
- Security Best Practices
- Deployment
- Troubleshooting
Overview
This integration provides:
- Type-safe API client for fetching telemetry data
- Server Components for SSR performance and SEO
- Client Components with interactive Chart.js visualizations
- Pre-built charts matching the telemetry collector dashboard
- API key authentication for secure data access
Prerequisites
- Node.js 18+ (for Next.js App Router)
- Next.js 13.4+ (with App Router)
- Access to a Container Census telemetry collector instance
- API key for stats endpoints (set via
STATS_API_KEYenv var on collector)
Installation
1. Copy Files to Your Project
Copy the contents of this directory to your Next.js project:
cp -r examples/nextjs/lib/* your-nextjs-app/lib/
cp -r examples/nextjs/components/* your-nextjs-app/components/
cp -r examples/nextjs/app/* your-nextjs-app/app/
2. Install Dependencies
npm install chart.js
# or
yarn add chart.js
# or
pnpm add chart.js
3. Configure TypeScript (if needed)
Ensure your tsconfig.json includes:
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
Or update import paths in the copied files to match your project structure.
Configuration
Environment Variables
Create a .env.local file in your Next.js project root:
# Required: Telemetry collector API base URL
TELEMETRY_API_URL=https://telemetry.example.com
# Required: API key for authentication
TELEMETRY_API_KEY=your-api-key-here
Important: These environment variables are server-side only and will not be exposed to the browser. This keeps your API key secure.
Telemetry Collector Setup
On your telemetry collector instance, set the following environment variables:
# Generate a secure random API key
STATS_API_KEY=your-secure-api-key-here
# Optional: Enable Basic Auth for the dashboard UI
COLLECTOR_AUTH_ENABLED=true
COLLECTOR_AUTH_USERNAME=admin
COLLECTOR_AUTH_PASSWORD=secure-password
To generate a secure API key:
# Linux/macOS
openssl rand -hex 32
# Or use Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Usage
Quick Start
The easiest way to get started is to use the pre-built dashboard page:
- Copy
app/telemetry/page.tsxto your Next.js app - Visit
http://localhost:3000/telemetryto see the dashboard
API Client
The API client provides type-safe methods for fetching telemetry data:
import { createTelemetryAPI } from '@/lib/telemetry-api';
// In a Server Component
export default async function MyPage() {
const api = createTelemetryAPI();
const summary = await api.getSummary();
return (
<div>
<h1>Total Installations: {summary.installations}</h1>
<p>Containers: {summary.total_containers}</p>
</div>
);
}
Server Components
Server Components fetch data at build time or on request, providing better SEO and initial load performance:
// app/stats/page.tsx
import { TelemetryStats } from '@/components/TelemetryStats';
export default function StatsPage() {
return (
<div>
<h1>Telemetry Statistics</h1>
<TelemetryStats />
</div>
);
}
Client Components
Client Components enable interactive charts with tooltips and animations:
// app/charts/page.tsx
import { createTelemetryAPI } from '@/lib/telemetry-api';
import { TopImagesChart } from '@/components/TopImagesChart';
export default async function ChartsPage() {
const api = createTelemetryAPI();
const images = await api.getTopImages({ limit: 10 });
return (
<div>
<h1>Top Container Images</h1>
<TopImagesChart images={images} />
</div>
);
}
Available Components
Server Components
TelemetryStats
Displays summary statistics in a card grid.
import { TelemetryStats } from '@/components/TelemetryStats';
<TelemetryStats />
Client Components (Interactive Charts)
All chart components accept data fetched from the API and render interactive Chart.js visualizations.
TopImagesChart
Horizontal bar chart of most popular container images.
import { TopImagesChart } from '@/components/TopImagesChart';
<TopImagesChart images={data} title="Most Popular Images" />
Props:
images: ImageCount[]- Array of image usage datatitle?: string- Chart title (default: "Top Container Images")
GrowthChart
Line chart showing installation growth and average container count over time.
import { GrowthChart } from '@/components/GrowthChart';
<GrowthChart data={growth} title="Growth Trends" />
Props:
data: Growth[]- Array of growth data pointstitle?: string- Chart title (default: "Growth Over Time")
RegistryChart
Doughnut chart showing container registry distribution.
import { RegistryChart } from '@/components/RegistryChart';
<RegistryChart data={registries} />
Props:
data: RegistryCount[]- Array of registry usage datatitle?: string- Chart title (default: "Registry Distribution")
VersionChart
Bar chart showing Container Census version adoption.
import { VersionChart } from '@/components/VersionChart';
<VersionChart data={versions} />
Props:
data: VersionCount[]- Array of version usage datatitle?: string- Chart title (default: "Version Adoption")
GeographyChart
Bar chart showing geographic distribution based on timezone data.
import { GeographyChart } from '@/components/GeographyChart';
<GeographyChart data={geography} />
Props:
data: GeographyData[]- Array of geographic datatitle?: string- Chart title (default: "Geographic Distribution")
ContainerImagesTable
Interactive, sortable, filterable table displaying detailed container image data with pagination.
import { ContainerImagesTable } from '@/components/ContainerImagesTable';
<ContainerImagesTable images={imageDetails} title="All Container Images" />
Props:
images: ImageDetail[]- Array of detailed image data (includes name, count, registry, installation count)
Connection & Architecture Charts
These components visualize container connectivity patterns, Docker Compose adoption, network usage, and volume sharing.
ComposeAdoptionChart
Pie chart showing Docker Compose adoption percentage.
import { ComposeAdoptionChart } from '@/components/ComposeAdoptionChart';
<ComposeAdoptionChart metrics={connectionMetrics} />
Props:
metrics: ConnectionMetrics- Connection metrics datatitle?: string- Chart title (default: "🐳 Docker Compose Adoption")
ConnectivityChart
Bar chart showing container connectivity metrics including average connections, dependencies, and compose projects.
import { ConnectivityChart } from '@/components/ConnectivityChart';
<ConnectivityChart metrics={connectionMetrics} />
Props:
metrics: ConnectionMetrics- Connection metrics datatitle?: string- Chart title (default: "🔗 Container Connectivity")
SharedVolumesChart
Doughnut chart showing volume sharing patterns across containers.
import { SharedVolumesChart } from '@/components/SharedVolumesChart';
<SharedVolumesChart metrics={connectionMetrics} />
Props:
metrics: ConnectionMetrics- Connection metrics datatitle?: string- Chart title (default: "📦 Shared Volumes Usage")
CustomNetworksChart
Bar chart comparing custom networks vs default Docker networks.
import { CustomNetworksChart } from '@/components/CustomNetworksChart';
<CustomNetworksChart metrics={connectionMetrics} />
Props:
metrics: ConnectionMetrics- Connection metrics datatitle?: string- Chart title (default: "🕸️ Custom Networks")
Features:
- Client-side search/filtering by image name
- Sortable columns (name, container count)
- Pagination (50 items per page)
- Color-coded registry badges (Docker Hub, GHCR, Quay, GCR, MCR)
- Percentage of total containers calculation
- Installation count per image
- Responsive design with Tailwind CSS
Trending Statistics Charts
These components visualize trends in container image popularity, week-over-week changes, and newly discovered images.
HottestImagesChart
Dual-ranking horizontal bar chart showing the most popular container images by both container count and adoption rate.
import { HottestImagesChart } from '@/components/HottestImagesChart';
import { createTelemetryAPI } from '@/lib/telemetry-api';
const api = createTelemetryAPI();
const hottest = await api.getHottest({ limit: 10, days: 7 });
<HottestImagesChart data={hottest} title="Hottest Container Images" />
Props:
data: HottestResponse- Hottest images data withby_containersandby_adoptionarraystitle?: string- Chart title (default: "Hottest Container Images")
Features:
- Toggle between "By Containers" and "By Adoption" views
- Medal badges for top 3 positions (🥇🥈🥉)
- Rich tooltips with container count and adoption percentage
- Color-coded bars by rank
MoversChart
Two-column layout showing week-over-week biggest gainers (risers) and losers (fallers).
import { MoversChart } from '@/components/MoversChart';
import { createTelemetryAPI } from '@/lib/telemetry-api';
const api = createTelemetryAPI();
const movers = await api.getMovers({ limit: 10, weeks: 1 });
<MoversChart data={movers} title="Biggest Movers This Week" />
Props:
data: MoversResponse- Movers data withrisersandfallersarraystitle?: string- Chart title (default: "Biggest Movers This Week")
Features:
- Side-by-side risers (green) and fallers (red) columns
- Change percentage badges
- Previous → Current count display
- Installation count indicators
- Rank badges with medals for top 3
NewEntriesCard
Card grid layout displaying newly discovered container images that have reached significant adoption.
import { NewEntriesCard } from '@/components/NewEntriesCard';
import { createTelemetryAPI } from '@/lib/telemetry-api';
const api = createTelemetryAPI();
const newEntries = await api.getNewEntries({ limit: 12, days: 30 });
<NewEntriesCard data={newEntries} title="New Entries" />
Props:
data: NewEntriesResponse- New entries data withnew_imagesarraytitle?: string- Card title (default: "New Entries")
Features:
- "NEW" badge with pulse animation for very recent images
- Days since first seen indicator
- Adoption percentage progress bar
- Container and installation counts
- Responsive grid layout (1-4 columns)
API Reference
TelemetryAPI Class
import { TelemetryAPI } from '@/lib/telemetry-api';
const api = new TelemetryAPI({
baseURL: 'https://telemetry.example.com',
apiKey: 'your-api-key'
});
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
getSummary() |
- | Promise<Summary> |
Get overview statistics |
getTopImages() |
{ limit?, days? } |
Promise<ImageCount[]> |
Get most popular images |
getGrowth() |
{ days? } |
Promise<Growth[]> |
Get growth metrics |
getRegistries() |
{ days? } |
Promise<RegistryCount[]> |
Get registry distribution |
getVersions() |
- | Promise<VersionCount[]> |
Get version distribution |
getGeography() |
- | Promise<GeographyData[]> |
Get geographic distribution |
getActivityHeatmap() |
{ days? } |
Promise<HeatmapData[]> |
Get activity heatmap data |
getScanIntervals() |
- | Promise<IntervalCount[]> |
Get scan interval distribution |
getRecentEvents() |
{ limit?, since? } |
Promise<SubmissionEvent[]> |
Get recent submissions |
getInstallations() |
{ days? } |
Promise<{...}> |
Get installation count |
getImageDetails() |
{ limit?, offset?, days?, search?, sort_by?, sort_order? } |
Promise<ImageDetailsResponse> |
Get detailed image data with pagination and search |
getConnectionMetrics() |
{ days? } |
Promise<ConnectionMetrics> |
Get container connectivity and architecture metrics |
getHottest() |
{ limit?, days?, metric? } |
Promise<HottestResponse> |
Get hottest images by containers and/or adoption |
getMovers() |
{ limit?, weeks?, min_installations? } |
Promise<MoversResponse> |
Get week-over-week biggest risers and fallers |
getNewEntries() |
{ limit?, days?, min_installations? } |
Promise<NewEntriesResponse> |
Get newly discovered images with significant adoption |
Data Types
All TypeScript types are exported from @/lib/telemetry-api:
import {
Summary,
ImageCount,
Growth,
RegistryCount,
VersionCount,
GeographyData,
HeatmapData,
IntervalCount,
SubmissionEvent,
ImageDetail,
ImageDetailsResponse,
ConnectionMetrics,
HotImage,
HottestResponse,
Mover,
MoversResponse,
NewEntry,
NewEntriesResponse
} from '@/lib/telemetry-api';
Image Details Types:
interface ImageDetail {
image: string; // Container image name (normalized)
count: number; // Number of containers using this image
registry: string; // Registry source (Docker Hub, GHCR, etc.)
installation_count: number; // Number of installations using this image
}
interface ImageDetailsResponse {
images: ImageDetail[];
pagination: {
total: number; // Total number of images
limit: number; // Page size
offset: number; // Current offset
};
}
Connection Metrics Type:
interface ConnectionMetrics {
total_containers: number; // Total containers across all installations
compose_project_count: number; // Number of unique Docker Compose projects
containers_in_compose: number; // Containers managed by Compose
compose_percentage: number; // Percentage of containers using Compose
network_count: number; // Total Docker networks
custom_network_count: number; // User-created networks (excludes bridge, host, none)
shared_volume_count: number; // Volumes shared by 2+ containers
total_volumes: number; // Estimated total volumes
containers_with_deps: number; // Containers with depends_on configured
total_dependencies: number; // Total dependency relationships
avg_connections_per_container: number; // Average network + volume connections per container
installations: number; // Number of installations reporting this data
}
Trending Statistics Types:
interface HotImage {
rank: number; // Position in the ranking
image: string; // Normalized image name (no registry prefix or tag)
total_containers: number; // Total container instances across all installations
installation_count: number; // Number of unique installations using this image
adoption_percentage: number; // Percentage of installations using this image
}
interface HottestResponse {
by_containers?: HotImage[]; // Ranked by total container count
by_adoption?: HotImage[]; // Ranked by adoption percentage
total_installations: number; // Total installations in the period
period_days: number; // Number of days included
}
interface Mover {
rank: number; // Position in risers/fallers list
image: string; // Normalized image name
current_count: number; // Current week container count
previous_count: number; // Previous week container count
change: number; // Absolute change (current - previous)
change_percentage: number; // Percentage change
current_installations: number; // Current week installation count
previous_installations: number; // Previous week installation count
}
interface MoversResponse {
risers: Mover[]; // Images that gained the most containers
fallers: Mover[]; // Images that lost the most containers
comparison_weeks: number; // Number of weeks compared
current_week: string; // Current week start date (YYYY-MM-DD)
previous_week: string; // Previous week start date (YYYY-MM-DD)
min_installations: number; // Minimum installations threshold used
}
interface NewEntry {
rank: number; // Position in the list
image: string; // Normalized image name
first_seen: string; // Date first observed (YYYY-MM-DD)
days_since_first_seen: number; // Days since first seen
total_containers: number; // Current total container count
installation_count: number; // Current installation count
adoption_percentage: number; // Percentage of installations using this image
}
interface NewEntriesResponse {
new_images: NewEntry[]; // List of newly discovered images
period_days: number; // Lookback period in days
min_installations: number; // Minimum installations threshold used
total_new_images: number; // Total count of new images
}
Security Best Practices
1. Keep API Keys Server-Side
✅ DO: Use environment variables accessed only in Server Components
// Server Component - ✅ SAFE
const api = createTelemetryAPI(); // Uses process.env
❌ DON'T: Expose API keys in Client Components or browser code
// Client Component - ❌ UNSAFE
'use client';
const apiKey = process.env.TELEMETRY_API_KEY; // This won't work anyway
2. Use Next.js Environment Variable Conventions
- Prefix public variables with
NEXT_PUBLIC_(but don't do this for API keys) - Server-only variables (like
TELEMETRY_API_KEY) are automatically protected
3. Implement Rate Limiting
For public-facing sites, implement your own rate limiting:
// middleware.ts
import { NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'),
});
export async function middleware(request: Request) {
if (request.url.includes('/telemetry')) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
const { success } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse('Rate limit exceeded', { status: 429 });
}
}
return NextResponse.next();
}
4. Use HTTPS in Production
Always use HTTPS for your telemetry collector API in production:
# ✅ GOOD
TELEMETRY_API_URL=https://telemetry.example.com
# ❌ BAD (development only)
TELEMETRY_API_URL=http://telemetry.example.com
Deployment
Vercel
-
Add environment variables in Project Settings → Environment Variables:
TELEMETRY_API_URLTELEMETRY_API_KEY
-
Deploy:
vercel
Docker
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
ENV TELEMETRY_API_URL=https://telemetry.example.com
ENV TELEMETRY_API_KEY=your-api-key
EXPOSE 3000
CMD ["npm", "start"]
Static Export (ISR)
For static sites with Incremental Static Regeneration:
// app/telemetry/page.tsx
export const revalidate = 300; // Revalidate every 5 minutes
export default async function Page() {
const api = createTelemetryAPI();
const data = await api.getSummary();
return <div>{/* ... */}</div>;
}
Troubleshooting
"TELEMETRY_API_URL is not set" Error
Problem: Environment variable not loaded
Solutions:
- Ensure
.env.localexists in project root - Restart Next.js dev server (
npm run dev) - Check variable names (no typos)
"Invalid or missing API key" (401 Error)
Problem: API key authentication failed
Solutions:
- Verify
TELEMETRY_API_KEYmatchesSTATS_API_KEYon collector - Check for whitespace in environment variables
- Restart both Next.js and telemetry collector services
CORS Errors in Browser
Problem: Cross-origin requests blocked
Solution: CORS is handled by the API middleware, but verify:
- Using correct API endpoints (should include
/api/stats/) - API requests from Server Components (not Client Components making direct fetch calls)
If you need Client Components to fetch directly:
// Create an API route proxy
// app/api/telemetry/[...path]/route.ts
import { createTelemetryAPI } from '@/lib/telemetry-api';
export async function GET(request: Request) {
const api = createTelemetryAPI();
// Forward request to telemetry API
// This keeps API key server-side
}
Charts Not Rendering
Problem: Chart.js not loading or canvas errors
Solutions:
- Ensure
chart.jsis installed:npm list chart.js - Check browser console for JavaScript errors
- Verify component is marked with
'use client'directive - Confirm data is not empty
Stale Data Showing
Problem: Data not updating
Solutions:
- Check
revalidatesetting in page component - Clear Next.js cache:
rm -rf .next - Verify telemetry collector is receiving new data
Examples
Custom Styling
Customize chart colors to match your brand:
// components/BrandedTopImagesChart.tsx
'use client';
import { TopImagesChart } from '@/components/TopImagesChart';
import type { ImageCount } from '@/lib/telemetry-api';
// Override colorPalette with your brand colors
const brandColors = ['#FF0000', '#00FF00', '#0000FF'];
export function BrandedTopImagesChart({ images }: { images: ImageCount[] }) {
// Modify the component to use brandColors
return <TopImagesChart images={images} />;
}
Filtering Data
Show only specific data:
export default async function FilteredPage() {
const api = createTelemetryAPI();
const allImages = await api.getTopImages({ limit: 100 });
// Filter to only show official images
const officialImages = allImages.filter(img =>
!img.image.includes('/')
);
return <TopImagesChart images={officialImages} />;
}
Multiple Dashboards
Create different views for different audiences:
// app/public-stats/page.tsx - Public view
export default async function PublicStats() {
const api = createTelemetryAPI();
const summary = await api.getSummary();
return (
<div>
<h1>{summary.installations} Installations</h1>
<p>Join the community!</p>
</div>
);
}
// app/admin/analytics/page.tsx - Admin view
import { headers } from 'next/headers';
export default async function AdminAnalytics() {
// Check authentication
const headersList = headers();
const session = await getSession(headersList);
if (!session?.isAdmin) {
return <div>Unauthorized</div>;
}
// Full analytics dashboard
const api = createTelemetryAPI();
const [summary, growth, images] = await Promise.all([
api.getSummary(),
api.getGrowth({ days: 365 }),
api.getTopImages({ limit: 50 })
]);
return (
<div>
{/* Comprehensive dashboard */}
</div>
);
}
Support
For issues or questions:
- Container Census Issues: https://github.com/yourusername/container-census/issues
- Next.js Documentation: https://nextjs.org/docs
- Chart.js Documentation: https://www.chartjs.org/docs/
License
This integration code is provided as part of the Container Census project. See the main project LICENSE file for details.