diff --git a/Dockerfile b/Dockerfile index 0982624..b5672c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,47 @@ -# Use the official bun image as a base -FROM oven/bun:latest +# Multi-stage build for optimized image size +FROM oven/bun:latest AS builder -# Set the working directory WORKDIR /app -# Create a volume for the working directory -VOLUME /app - -# Copy project files -COPY . . +# Copy package files first for better caching +COPY package.json bun.lock ./ +COPY admin-panel/package.json ./admin-panel/ +COPY frontend/package.json ./frontend/ # Install dependencies RUN bun install -RUN bun run build:frontend -RUN bun run build:admin +# Copy source files +COPY . . -# Expose the ports your app runs on +# Build frontend and admin panel +RUN bun run build:frontend && bun run build:admin + +# Production stage +FROM oven/bun:latest AS runtime + +WORKDIR /app + +# Create non-root user for security +RUN addgroup --system --gid 1001 bunjs && \ + adduser --system --uid 1001 bunjs + +# Copy built application from builder stage +COPY --from=builder --chown=bunjs:bunjs /app . + +# Create necessary directories with proper permissions +RUN mkdir -p data public/podcast_audio && \ + chown -R bunjs:bunjs data public + +# Switch to non-root user +USER bunjs + +# Expose ports EXPOSE 3000 3001 -# Start both servers +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD bun -e "fetch('http://localhost:3000').then(() => process.exit(0)).catch(() => process.exit(1))" + +# Start both servers with proper process management CMD ["sh", "-c", "bun run /app/server.ts & bun run /app/admin-server.ts & wait"] diff --git a/build-docker-image.sh b/build-docker-image.sh old mode 100644 new mode 100755 index 2aa15a5..1fb7f81 --- a/build-docker-image.sh +++ b/build-docker-image.sh @@ -1,4 +1,32 @@ #!/bin/bash +set -euo pipefail # Build Docker image for Voice RSS Summary project -docker build -t voice-rss-summary . +# Usage: ./build-docker-image.sh [tag] [build-args...] + +IMAGE_NAME="voice-rss-summary" +TAG="${1:-latest}" +FULL_TAG="${IMAGE_NAME}:${TAG}" + +echo "Building Docker image: ${FULL_TAG}" +echo "Build context: $(pwd)" + +# Check if Dockerfile exists +if [[ ! -f "Dockerfile" ]]; then + echo "Error: Dockerfile not found in current directory" + exit 1 +fi + +# Build with build cache and progress output +docker build \ + --tag "${FULL_TAG}" \ + --progress=plain \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + "${@:2}" \ + . + +# Display image info +echo "\nBuild completed successfully!" +echo "Image: ${FULL_TAG}" +echo "Size: $(docker images --format 'table {{.Size}}' "${FULL_TAG}" | tail -n +2)" +echo "\nTo run the container, use: ./run-docker.sh" diff --git a/run-docker.sh b/run-docker.sh index dc55741..55d6e7c 100755 --- a/run-docker.sh +++ b/run-docker.sh @@ -1,14 +1,66 @@ #!/bin/bash +set -euo pipefail -# Run Docker container with volume mounts for feed_urls.txt and .env +# Run Docker container for Voice RSS Summary project +# Usage: ./run-docker.sh [container-name] [image-tag] + +IMAGE_NAME="voice-rss-summary" +CONTAINER_NAME="${1:-voice-rss-summary}" +IMAGE_TAG="${2:-latest}" +FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "Starting Docker container: ${CONTAINER_NAME}" +echo "Using image: ${FULL_IMAGE}" + +# Check if image exists +if ! docker image inspect "${FULL_IMAGE}" >/dev/null 2>&1; then + echo "Error: Docker image '${FULL_IMAGE}' not found" + echo "Build it first with: ./build-docker-image.sh" + exit 1 +fi + +# Stop and remove existing container if it exists +if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "Stopping and removing existing container: ${CONTAINER_NAME}" + docker stop "${CONTAINER_NAME}" >/dev/null 2>&1 || true + docker rm "${CONTAINER_NAME}" >/dev/null 2>&1 || true +fi + +# Create required directories if they don't exist +mkdir -p "$(pwd)/public/podcast_audio" "$(pwd)/data" + +# Check for required files +if [[ ! -f "$(pwd)/.env" ]]; then + echo "Warning: .env file not found. Create one with required environment variables." +fi + +if [[ ! -f "$(pwd)/feed_urls.txt" ]]; then + echo "Warning: feed_urls.txt not found. Creating empty file." + touch "$(pwd)/feed_urls.txt" +fi + +# Run container with proper volume mounts and networking docker run \ - --volume "$(pwd)/feed_urls.txt:/app/feed_urls.txt" \ - --volume "$(pwd)/.env:/app/.env" \ - --volume "$(pwd)/public:/app/public" \ - --volume "$(pwd)/data:/app/data" \ - --publish 3000:3000 \ - --publish 3001:3001 \ - --name voice-rss-summary \ - -d \ - --restart always \ - voice-rss-summary + --detach \ + --name "${CONTAINER_NAME}" \ + --restart unless-stopped \ + --publish 3000:3000 \ + --publish 3001:3001 \ + --volume "$(pwd)/feed_urls.txt:/app/feed_urls.txt:ro" \ + --volume "$(pwd)/.env:/app/.env:ro" \ + --volume "$(pwd)/public:/app/public" \ + --volume "$(pwd)/data:/app/data" \ + --health-cmd="bun -e 'fetch(\"http://localhost:3000\").then(() => process.exit(0)).catch(() => process.exit(1))'" \ + --health-interval=30s \ + --health-timeout=3s \ + --health-start-period=10s \ + --health-retries=3 \ + "${FULL_IMAGE}" + +echo "\nContainer started successfully!" +echo "Container name: ${CONTAINER_NAME}" +echo "Web UI: http://localhost:3000" +echo "Admin panel: http://localhost:3001" +echo "\nTo view logs: docker logs -f ${CONTAINER_NAME}" +echo "To stop: docker stop ${CONTAINER_NAME}" +echo "To remove: docker rm ${CONTAINER_NAME}"