Skip to content

Commit c36c4d9

Browse files
committed
feat: add Docker Compose configuration for multi-service setup; include PostgreSQL, Redis, and MinIO services with health checks and environment variables
1 parent fcbc868 commit c36c4d9

6 files changed

Lines changed: 232 additions & 136 deletions

File tree

.github/workflows/docker-build.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Build and Push Docker Image
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ["v*"]
7+
pull_request:
8+
branches: [main]
9+
workflow_dispatch:
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
env:
16+
IMAGE_NAME: bysages/lens
17+
NIXPACKS_NODE_VERSION: 22
18+
19+
jobs:
20+
build:
21+
runs-on: ubuntu-latest
22+
23+
permissions:
24+
contents: read
25+
packages: write
26+
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Log in to Docker Hub
31+
if: github.event_name != 'pull_request'
32+
uses: docker/login-action@v3
33+
with:
34+
username: ${{ secrets.DOCKER_USERNAME }}
35+
password: ${{ secrets.DOCKER_PASSWORD }}
36+
37+
- name: Extract metadata for tags
38+
id: meta
39+
run: |
40+
if [[ $GITHUB_REF == refs/tags/* ]]; then
41+
VERSION=${GITHUB_REF#refs/tags/}
42+
echo "tags<<EOF" >> $GITHUB_OUTPUT
43+
echo "${{ env.IMAGE_NAME }}:${VERSION}" >> $GITHUB_OUTPUT
44+
echo "${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT
45+
echo "EOF" >> $GITHUB_OUTPUT
46+
else
47+
echo "tags<<EOF" >> $GITHUB_OUTPUT
48+
echo "${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT
49+
echo "${{ env.IMAGE_NAME }}:sha-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
50+
echo "EOF" >> $GITHUB_OUTPUT
51+
fi
52+
53+
- name: Build and push Docker images
54+
uses: iloveitaly/github-action-nixpacks@main
55+
with:
56+
push: ${{ github.event_name != 'pull_request' }}
57+
cache: true
58+
cache_tag: ${{ env.IMAGE_NAME }}:cache
59+
tags: ${{ steps.meta.outputs.tags }}
60+
env: |
61+
NIXPACKS_NODE_VERSION=${{ env.NIXPACKS_NODE_VERSION }}

docker-compose.yml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
services:
2+
# Main application service
3+
lens:
4+
image: bysages/lens:latest
5+
container_name: lens-app
6+
restart: unless-stopped
7+
ports:
8+
- "3000:3000"
9+
environment:
10+
- NODE_ENV=production
11+
- DATABASE_URL=postgresql://lens:${POSTGRES_PASSWORD:-lens123}@postgres:5432/lens
12+
- REDIS_URL=redis://redis:6379
13+
- MINIO_ENDPOINT=http://minio:9000
14+
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
15+
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
16+
- MINIO_BUCKET=lens
17+
- MINIO_REGION=us-east-1
18+
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your-secret-key-change-this}
19+
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}
20+
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-}
21+
- ALLOWED_DOMAINS=${ALLOWED_DOMAINS:-}
22+
depends_on:
23+
postgres:
24+
condition: service_healthy
25+
redis:
26+
condition: service_healthy
27+
minio:
28+
condition: service_started
29+
networks:
30+
- lens-network
31+
volumes:
32+
- lens-cache:/app/.cache
33+
- lens-uploads:/app/uploads
34+
35+
# PostgreSQL database
36+
postgres:
37+
image: postgres:16-alpine
38+
container_name: lens-postgres
39+
restart: unless-stopped
40+
environment:
41+
- POSTGRES_DB=lens
42+
- POSTGRES_USER=lens
43+
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-lens123}
44+
- POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
45+
volumes:
46+
- postgres-data:/var/lib/postgresql/data
47+
networks:
48+
- lens-network
49+
healthcheck:
50+
test: ["CMD-SHELL", "pg_isready -U lens -d lens"]
51+
interval: 10s
52+
timeout: 5s
53+
retries: 5
54+
start_period: 30s
55+
56+
# Redis cache
57+
redis:
58+
image: redis:7-alpine
59+
container_name: lens-redis
60+
restart: unless-stopped
61+
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
62+
volumes:
63+
- redis-data:/data
64+
networks:
65+
- lens-network
66+
healthcheck:
67+
test: ["CMD", "redis-cli", "ping"]
68+
interval: 10s
69+
timeout: 5s
70+
retries: 5
71+
72+
# MinIO S3-compatible storage
73+
minio:
74+
image: minio/minio:latest
75+
container_name: lens-minio
76+
restart: unless-stopped
77+
command: server /data --console-address ":9001"
78+
environment:
79+
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY:-minioadmin}
80+
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY:-minioadmin}
81+
volumes:
82+
- minio-data:/data
83+
networks:
84+
- lens-network
85+
healthcheck:
86+
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
87+
interval: 30s
88+
timeout: 20s
89+
retries: 3
90+
91+
# MinIO bucket initialization
92+
minio-init:
93+
image: minio/mc:latest
94+
container_name: lens-minio-init
95+
depends_on:
96+
minio:
97+
condition: service_healthy
98+
networks:
99+
- lens-network
100+
entrypoint: >
101+
/bin/sh -c "
102+
mc alias set minio http://minio:9000 ${MINIO_ACCESS_KEY:-minioadmin} ${MINIO_SECRET_KEY:-minioadmin};
103+
mc mb minio/lens --ignore-existing;
104+
mc anonymous set public minio/lens;
105+
exit 0;
106+
"
107+
108+
networks:
109+
lens-network:
110+
driver: bridge
111+
name: lens-network
112+
113+
volumes:
114+
postgres-data:
115+
name: lens-postgres-data
116+
redis-data:
117+
name: lens-redis-data
118+
minio-data:
119+
name: lens-minio-data
120+
lens-cache:
121+
name: lens-cache
122+
lens-uploads:
123+
name: lens-uploads

nixpacks.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ nixPkgs = [
1818
cmds = ["pnpm install"]
1919

2020
[phases.build]
21-
cmds = ["pnpm build"]
21+
cmds = [
22+
"pnpm exec playwright install chromium --with-deps",
23+
"pnpm build"
24+
]
2225

2326
[start]
2427
cmd = "pnpm preview"

pnpm-workspace.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
packages:
2+
# all packages in subdirs of packages/ and playground/
3+
- "packages/**"
4+
- "playground/**"
5+
# exclude packages that are inside test directories
6+
- "!**/test/**"
7+
18
onlyBuiltDependencies:
29
- "@parcel/watcher"
310
- "@prisma/client"

server/routes/index.ts

Lines changed: 25 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -4,127 +4,40 @@ import { isStorageHealthy } from "../utils/storage";
44
export default defineEventHandler(async (event) => {
55
const baseUrl = getRequestURL(event).origin;
66

7-
// Get system information
8-
const memoryUsage = process.memoryUsage();
9-
const startTime = Date.now() - process.uptime() * 1000;
10-
11-
// Get adaptive storage health
12-
const storageHealth = await isStorageHealthy();
7+
// Parallel execution for better performance
8+
const [storageHealth, memoryUsage] = await Promise.all([
9+
isStorageHealthy(),
10+
Promise.resolve(process.memoryUsage()),
11+
]);
1312

1413
const serviceStatus = {
15-
name: "Lens Multimedia API Platform",
16-
description:
17-
"High-performance multimedia API service for images, fonts, favicons, and web content",
18-
status: "running",
14+
name: "Lens API",
1915
version: pkg.version,
16+
status: "healthy",
2017
timestamp: new Date().toISOString(),
21-
uptime: {
22-
seconds: Math.floor(process.uptime()),
23-
human: new Date(process.uptime() * 1000).toISOString().substr(11, 8),
24-
startTime: new Date(startTime).toISOString(),
25-
},
26-
system: {
27-
nodeVersion: process.version,
28-
platform: process.platform,
29-
arch: process.arch,
30-
environment: process.env.NODE_ENV || "development",
31-
memory: {
32-
rss: `${Math.round(memoryUsage.rss / 1024 / 1024)}MB`,
33-
heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)}MB`,
34-
heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)}MB`,
35-
external: `${Math.round(memoryUsage.external / 1024 / 1024)}MB`,
36-
},
18+
uptime: Math.floor(process.uptime()),
19+
environment: process.env.NODE_ENV || "development",
20+
health: {
21+
storage: storageHealth ? "ok" : "degraded",
22+
memory: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)}MB`,
3723
},
38-
endpoints: {
39-
"Core Services": {
40-
"/favicon": "Favicon extraction and optimization",
41-
"/css": "Google Fonts CSS v1 API compatible",
42-
"/css2": "Google Fonts CSS v2 API compatible",
43-
"/fonts/*": "Font file proxy service",
44-
"/webfonts": "Google Fonts metadata API",
45-
"/og": "Open Graph image generation",
46-
"/img/*": "IPX-compatible image proxy and processing",
47-
"/screenshot": "Website screenshot capture service",
48-
description:
49-
"All endpoints work without authentication. Authenticated users get higher rate limits.",
50-
authentication:
51-
"Optional: Session token or API key (x-api-key header / api_key query parameter)",
52-
examples: [
53-
`${baseUrl}/favicon?url=https://github.com&size=64`,
54-
`${baseUrl}/css2?family=Inter:wght@400;700&display=swap`,
55-
`${baseUrl}/og?title=Hello World&description=My App&theme=blue`,
56-
`${baseUrl}/img/w_300,f_webp/https%3A//example.com/image.jpg`,
57-
`${baseUrl}/screenshot?url=https://example.com&width=1920&height=1080`,
58-
],
59-
},
60-
},
61-
features: [
62-
"🖼️ Website screenshot capture with Playwright",
63-
"🎨 Dynamic OG image generation",
64-
"🌐 Self-hosted Google Fonts replacement",
65-
"🖼️ IPX-compatible image proxy and optimization",
66-
"🚀 Platform compatibility validation",
67-
"💾 Multi-layer adaptive caching (Redis + FileSystem + Memory)",
68-
"🗄️ Adaptive database support (Turso/PostgreSQL/MySQL/SQLite)",
69-
"🔒 Flexible authentication with better-auth",
70-
"⚡ Rate limiting with unified plugin-level management",
71-
"📱 Mobile viewport and dark mode support",
72-
"🎯 CORS support and security headers",
24+
services: ["favicon", "fonts", "og", "img", "screenshot"],
25+
examples: [
26+
`${baseUrl}/favicon?url=github.com`,
27+
`${baseUrl}/og?title=Hello&theme=blue`,
28+
`${baseUrl}/img/w_300/example.com/image.jpg`,
7329
],
74-
configuration: {
75-
storage: {
76-
status: storageHealth ? "Healthy" : "Unhealthy",
77-
layers: storageHealth ? "Multi-layer adaptive" : "Memory fallback",
78-
responsive: storageHealth ? "Yes" : "No",
79-
drivers: {
80-
redis: process.env.REDIS_URL ? "Available" : "Not configured",
81-
filesystem: "Available (fallback)",
82-
memory: "Available",
83-
},
84-
ttl: {
85-
screenshots: "1 hour",
86-
ogImages: "1 day",
87-
favicons: "7 days",
88-
fonts: "30 days",
89-
metadata: "1 day",
90-
},
91-
},
92-
database: {
93-
type: process.env.TURSO_DATABASE_URL
94-
? "Turso"
95-
: process.env.DATABASE_URL?.startsWith("postgres")
96-
? "PostgreSQL"
97-
: process.env.DATABASE_URL?.startsWith("mysql")
98-
? "MySQL"
99-
: "SQLite (fallback)",
100-
status: "Connected",
101-
features: ["Kysely ORM", "Auto-migration", "TypeScript support"],
102-
},
103-
browser: {
104-
playwright: "Installed",
105-
config: process.env.PLAYWRIGHT_BROWSER_CONFIG || "auto",
106-
runtime: "Persistent Node.js Runtime",
107-
},
108-
auth: {
109-
github: process.env.GITHUB_CLIENT_ID ? "Configured" : "Not configured",
110-
emailPassword: "Enabled",
111-
sessionStorage: "Adaptive (secondary storage)",
112-
},
113-
domains: {
114-
allowed: process.env.ALLOWED_DOMAINS
115-
? process.env.ALLOWED_DOMAINS.split(",").length + " domains"
116-
: "All domains allowed",
117-
},
118-
},
119-
links: {
120-
repository: "https://github.com/bysages/lens",
121-
documentation: "https://github.com/bysages/lens#readme",
30+
features: ["screenshots", "og-images", "fonts", "image-proxy", "caching"],
31+
storage: {
32+
redis: !!process.env.REDIS_URL,
33+
minio: !!(process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY),
34+
s3: !!(process.env.S3_ENDPOINT && process.env.S3_ACCESS_KEY_ID),
12235
},
36+
docs: "https://github.com/bysages/lens#readme",
12337
};
12438

125-
// Return JSON response with proper headers
126-
setHeader(event, "Content-Type", "application/json");
127-
setHeader(event, "Cache-Control", "no-cache");
39+
// Cache for 30 seconds to reduce load
40+
setHeader(event, "Cache-Control", "public, max-age=30");
12841

12942
return serviceStatus;
13043
});

0 commit comments

Comments
 (0)