mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat: add Docker deployment support
This commit is contained in:
85
.dockerignore
Normal file
85
.dockerignore
Normal file
@@ -0,0 +1,85 @@
|
||||
# =============================================================================
|
||||
# Docker Build Exclusions
|
||||
# =============================================================================
|
||||
# These files are NOT sent to Docker during build, making builds faster
|
||||
# and images smaller.
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Dependencies (reinstalled during build)
|
||||
# -----------------------------------------------------------------------------
|
||||
node_modules/
|
||||
.npm/
|
||||
.pnpm-store/
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build outputs (rebuilt during build)
|
||||
# -----------------------------------------------------------------------------
|
||||
dist/
|
||||
.svelte-kit/
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# .NET build artifacts
|
||||
# -----------------------------------------------------------------------------
|
||||
src/services/parser/bin/
|
||||
src/services/parser/obj/
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Git (not needed in image)
|
||||
# -----------------------------------------------------------------------------
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# IDE and editor files
|
||||
# -----------------------------------------------------------------------------
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Documentation (not needed in image)
|
||||
# -----------------------------------------------------------------------------
|
||||
*.md
|
||||
!README.md
|
||||
docs/
|
||||
LICENSE
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Development and test files
|
||||
# -----------------------------------------------------------------------------
|
||||
.env
|
||||
.env.*
|
||||
*.log
|
||||
*.tmp
|
||||
temp/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Docker files themselves (prevent recursion)
|
||||
# -----------------------------------------------------------------------------
|
||||
Dockerfile*
|
||||
compose.yml
|
||||
compose.yaml
|
||||
docker-compose.yml
|
||||
docker-compose.yaml
|
||||
# Keep entrypoint script, ignore the rest
|
||||
!docker/entrypoint.sh
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# CI/CD
|
||||
# -----------------------------------------------------------------------------
|
||||
.github/
|
||||
.gitlab-ci.yml
|
||||
.travis.yml
|
||||
Jenkinsfile
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Misc
|
||||
# -----------------------------------------------------------------------------
|
||||
*.tgz
|
||||
*.tar.gz
|
||||
*.zip
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,6 +14,7 @@ CLAUDE.md
|
||||
|
||||
# Application
|
||||
/temp
|
||||
/config
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
98
Dockerfile
Normal file
98
Dockerfile
Normal file
@@ -0,0 +1,98 @@
|
||||
# =============================================================================
|
||||
# Profilarr Dockerfile
|
||||
# =============================================================================
|
||||
# Multi-stage build for minimal final image size
|
||||
#
|
||||
# Build: docker build -t profilarr .
|
||||
# Run: docker run -v ./config:/config -p 6868:6868 profilarr
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 1: Build
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM denoland/deno:2.5.6 AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy everything
|
||||
COPY . .
|
||||
|
||||
# Install dependencies (creates node_modules for npm packages)
|
||||
RUN deno install --node-modules-dir
|
||||
|
||||
# Build the application
|
||||
# 1. Vite builds SvelteKit to dist/build/
|
||||
# 2. Deno compiles to standalone binary
|
||||
ENV APP_BASE_PATH=/build/dist/build
|
||||
RUN deno run -A npm:vite build
|
||||
RUN deno compile \
|
||||
--no-check \
|
||||
--allow-net \
|
||||
--allow-read \
|
||||
--allow-write \
|
||||
--allow-env \
|
||||
--allow-ffi \
|
||||
--allow-run \
|
||||
--allow-sys \
|
||||
--target x86_64-unknown-linux-gnu \
|
||||
--output dist/build/profilarr \
|
||||
dist/build/mod.ts
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 2: Runtime
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM debian:12-slim
|
||||
|
||||
# Labels for container metadata
|
||||
LABEL org.opencontainers.image.title="Profilarr"
|
||||
LABEL org.opencontainers.image.description="Configuration management for Radarr and Sonarr"
|
||||
LABEL org.opencontainers.image.source="https://github.com/Dictionarry-Hub/profilarr"
|
||||
LABEL org.opencontainers.image.licenses="AGPL-3.0"
|
||||
|
||||
# Install runtime dependencies
|
||||
# - git: PCD repository operations (clone, pull, push)
|
||||
# - tar: Backup creation and restoration
|
||||
# - curl: Health checks
|
||||
# - gosu: Drop privileges to non-root user
|
||||
# - ca-certificates: HTTPS connections
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
tar \
|
||||
curl \
|
||||
gosu \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get clean
|
||||
|
||||
# Create application directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built application from builder stage
|
||||
COPY --from=builder /build/dist/build/profilarr /app/profilarr
|
||||
COPY --from=builder /build/dist/build/server.js /app/server.js
|
||||
COPY --from=builder /build/dist/build/static /app/static
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Create config directory
|
||||
RUN mkdir -p /config
|
||||
|
||||
# Environment variables
|
||||
ENV PORT=6868
|
||||
ENV HOST=0.0.0.0
|
||||
ENV APP_BASE_PATH=/config
|
||||
ENV TZ=UTC
|
||||
|
||||
# Expose port
|
||||
EXPOSE 6868
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD curl -sf http://localhost:${PORT}/api/v1/health || exit 1
|
||||
|
||||
# Volume for persistent data
|
||||
VOLUME /config
|
||||
|
||||
# Entrypoint handles PUID/PGID/UMASK then runs the app
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
60
Dockerfile.parser
Normal file
60
Dockerfile.parser
Normal file
@@ -0,0 +1,60 @@
|
||||
# =============================================================================
|
||||
# Profilarr Parser Dockerfile
|
||||
# =============================================================================
|
||||
# .NET 8.0 microservice for parsing release titles
|
||||
# This service is OPTIONAL - only needed for custom format/quality profile testing
|
||||
#
|
||||
# Build: docker build -f Dockerfile.parser -t profilarr-parser .
|
||||
# Run: docker run -p 5000:5000 profilarr-parser
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 1: Build
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy project file first for better layer caching
|
||||
COPY src/services/parser/Parser.csproj ./
|
||||
RUN dotnet restore
|
||||
|
||||
# Copy source and build
|
||||
COPY src/services/parser/ ./
|
||||
RUN dotnet publish -c Release -o /app --no-restore
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 2: Runtime
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
|
||||
|
||||
# Labels for container metadata
|
||||
LABEL org.opencontainers.image.title="Profilarr Parser"
|
||||
LABEL org.opencontainers.image.description="Release title parser for Profilarr (optional)"
|
||||
LABEL org.opencontainers.image.source="https://github.com/Dictionarry-Hub/profilarr"
|
||||
LABEL org.opencontainers.image.licenses="AGPL-3.0"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder /app ./
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1000 parser && \
|
||||
adduser -u 1000 -G parser -D -h /app parser
|
||||
|
||||
# Switch to non-root user
|
||||
USER parser
|
||||
|
||||
# Environment variables
|
||||
ENV ASPNETCORE_URLS=http://+:5000
|
||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||
|
||||
# Expose port
|
||||
EXPOSE 5000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||
CMD wget -qO- http://localhost:5000/health || exit 1
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT ["dotnet", "Parser.dll"]
|
||||
33
compose.dev.yml
Normal file
33
compose.dev.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Development compose - builds from source
|
||||
# Usage: docker compose -f compose.dev.yml up --build
|
||||
|
||||
services:
|
||||
profilarr:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: profilarr-dev
|
||||
ports:
|
||||
- "6868:6868"
|
||||
volumes:
|
||||
- ./config:/config
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- UMASK=022
|
||||
- TZ=Etc/UTC
|
||||
# - PORT=6868
|
||||
# - HOST=0.0.0.0
|
||||
- PARSER_HOST=parser
|
||||
- PARSER_PORT=5000
|
||||
depends_on:
|
||||
parser:
|
||||
condition: service_healthy
|
||||
|
||||
parser:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.parser
|
||||
container_name: profilarr-parser-dev
|
||||
expose:
|
||||
- "5000"
|
||||
29
compose.yml
Normal file
29
compose.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
profilarr:
|
||||
image: santiagosayshey/profilarr:latest
|
||||
container_name: profilarr
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6868:6868"
|
||||
volumes:
|
||||
- ./config:/config
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- UMASK=022
|
||||
- TZ=Etc/UTC
|
||||
# - PORT=6868
|
||||
# - HOST=0.0.0.0
|
||||
- PARSER_HOST=parser
|
||||
- PARSER_PORT=5000
|
||||
depends_on:
|
||||
parser:
|
||||
condition: service_healthy
|
||||
|
||||
# Optional - only needed for CF/QP testing features
|
||||
parser:
|
||||
image: santiagosayshey/profilarr-parser:latest
|
||||
container_name: profilarr-parser
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "5000"
|
||||
@@ -40,7 +40,11 @@
|
||||
"check:client": "npx svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env",
|
||||
"test:watch": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env --watch",
|
||||
"generate:api-types": "npx openapi-typescript docs/api/v1/openapi.yaml -o src/lib/api/v1.d.ts"
|
||||
"generate:api-types": "npx openapi-typescript docs/api/v1/openapi.yaml -o src/lib/api/v1.d.ts",
|
||||
"docker:build": "docker compose -f compose.dev.yml build --no-cache",
|
||||
"docker:up": "docker compose -f compose.dev.yml up --build",
|
||||
"docker:down": "docker compose -f compose.dev.yml down",
|
||||
"docker:clean": "docker compose -f compose.dev.yml down -v --rmi local"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"lib": ["deno.window", "dom"],
|
||||
|
||||
58
docker/entrypoint.sh
Executable file
58
docker/entrypoint.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Profilarr Container Entrypoint
|
||||
# =============================================================================
|
||||
# Handles PUID/PGID/UMASK setup for proper file permissions
|
||||
# All logging is handled by the application's startup module
|
||||
|
||||
set -e
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Configuration with defaults
|
||||
# -----------------------------------------------------------------------------
|
||||
PUID=${PUID:-1000}
|
||||
PGID=${PGID:-1000}
|
||||
UMASK=${UMASK:-022}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Create group if it doesn't exist
|
||||
# -----------------------------------------------------------------------------
|
||||
if ! getent group profilarr > /dev/null 2>&1; then
|
||||
groupadd -g "${PGID}" profilarr
|
||||
elif [ "$(getent group profilarr | cut -d: -f3)" != "${PGID}" ]; then
|
||||
groupmod -g "${PGID}" profilarr 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Create user if it doesn't exist
|
||||
# -----------------------------------------------------------------------------
|
||||
if ! getent passwd profilarr > /dev/null 2>&1; then
|
||||
useradd -u "${PUID}" -g "${PGID}" -d /config -s /bin/bash profilarr
|
||||
elif [ "$(id -u profilarr)" != "${PUID}" ]; then
|
||||
usermod -u "${PUID}" profilarr 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Ensure user is in the correct group
|
||||
# -----------------------------------------------------------------------------
|
||||
usermod -g "${PGID}" profilarr >/dev/null 2>&1 || true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Set umask
|
||||
# -----------------------------------------------------------------------------
|
||||
umask "${UMASK}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Create config directory structure if it doesn't exist
|
||||
# -----------------------------------------------------------------------------
|
||||
mkdir -p /config/data /config/logs /config/backups /config/databases
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Fix ownership of config directory
|
||||
# -----------------------------------------------------------------------------
|
||||
chown -R "${PUID}:${PGID}" /config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Drop privileges and run the application
|
||||
# -----------------------------------------------------------------------------
|
||||
exec gosu profilarr /app/profilarr
|
||||
@@ -1,5 +1,5 @@
|
||||
import { config } from '$config';
|
||||
import { printBanner, getServerInfo } from '$logger/startup.ts';
|
||||
import { printBanner, getServerInfo, logContainerConfig } from '$logger/startup.ts';
|
||||
import { logSettings } from '$logger/settings.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
import { db } from '$db/db.ts';
|
||||
@@ -20,6 +20,9 @@ await runMigrations();
|
||||
// Load log settings from database (must be after migrations)
|
||||
logSettings.load();
|
||||
|
||||
// Log container config (if running in Docker)
|
||||
await logContainerConfig();
|
||||
|
||||
// Initialize PCD caches (must be after migrations and log settings)
|
||||
await pcdManager.initialize();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { config } from '$config';
|
||||
import { appInfoQueries } from '$db/queries/appInfo.ts';
|
||||
import { logger } from './logger.ts';
|
||||
|
||||
const BANNER = String.raw`
|
||||
_____.__.__
|
||||
@@ -14,6 +15,42 @@ _____________ _____/ ____\__| | _____ ______________
|
||||
|__| \/
|
||||
`;
|
||||
|
||||
/**
|
||||
* Check if running inside a Docker container
|
||||
*/
|
||||
function isDocker(): boolean {
|
||||
try {
|
||||
// Check for .dockerenv file (most reliable)
|
||||
Deno.statSync('/.dockerenv');
|
||||
return true;
|
||||
} catch {
|
||||
// Check for docker in cgroup (fallback)
|
||||
try {
|
||||
const cgroup = Deno.readTextFileSync('/proc/1/cgroup');
|
||||
return cgroup.includes('docker');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log container configuration (only when running in Docker)
|
||||
*/
|
||||
export async function logContainerConfig(): Promise<void> {
|
||||
if (!isDocker()) return;
|
||||
|
||||
await logger.info('Container initialized', {
|
||||
source: 'Docker',
|
||||
meta: {
|
||||
puid: Deno.env.get('PUID') || '1000',
|
||||
pgid: Deno.env.get('PGID') || '1000',
|
||||
umask: Deno.env.get('UMASK') || '022',
|
||||
tz: Deno.env.get('TZ') || 'UTC'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function printBanner(): void {
|
||||
const version = appInfoQueries.getVersion();
|
||||
const url = config.serverUrl;
|
||||
|
||||
Reference in New Issue
Block a user