mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-11 17:26:20 +02:00
fix: single container without nginx — Express serves frontend
- Remove separate backend/ and frontend/ Dockerfiles - Add root Dockerfile (3-stage: frontend build → backend build → runtime) - Frontend static files served by Express from /public - Simplify docker-compose to 3 services: mongodb, redis, app (port 3579) - Fix vite proxy not to strip /api prefix from paths - Default API URL to relative /api instead of hardcoded localhost Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a8787857b5
commit
1822ab1afe
9 files changed
+157
-480
No files matched your search
+3
-2
@@ -22,5 +22,6 @@ JWT_REFRESH_EXPIRES_IN=30d
|
|||||||
PODCAST_INDEX_API_KEY=
|
PODCAST_INDEX_API_KEY=
|
||||||
PODCAST_INDEX_API_SECRET=
|
PODCAST_INDEX_API_SECRET=
|
||||||
|
|
||||||
# Frontend Configuration
|
# Frontend Configuration (only needed for local dev without Docker)
|
||||||
VITE_API_URL=http://localhost:5000/api
|
# In Docker, the frontend is served by the backend itself — no VITE_API_URL needed
|
||||||
|
VITE_API_URL=/api
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
# Stage 1: Build frontend
|
||||||
|
FROM node:20-alpine AS frontend-builder
|
||||||
|
|
||||||
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY frontend/ ./
|
||||||
|
|
||||||
|
# Build with /api as the API base URL (served by Express on same host)
|
||||||
|
ENV VITE_API_URL=/api
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 2: Build backend
|
||||||
|
FROM node:20-alpine AS backend-builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY backend/tsconfig.json ./
|
||||||
|
COPY backend/src ./src
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 3: Production image
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy backend production deps
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# Copy compiled backend
|
||||||
|
COPY --from=backend-builder /app/dist ./dist
|
||||||
|
|
||||||
|
# Copy frontend build into public/ (backend will serve it as static files)
|
||||||
|
COPY --from=frontend-builder /frontend/dist ./public
|
||||||
|
|
||||||
|
EXPOSE 3579
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3579
|
||||||
|
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
+81
-406
@@ -1,458 +1,133 @@
|
|||||||
# 🐳 Podcastic - Unraid Installation Guide
|
# Podcastic - Unraid Installation Guide
|
||||||
|
|
||||||
Complete guide to install Podcastic on Unraid as a **single all-in-one container**.
|
Single container on port **3579** — includes backend, frontend, and serves everything via Express.
|
||||||
|
|
||||||
## 📋 What's Included
|
## What's Included
|
||||||
|
|
||||||
This single Docker container includes:
|
This single Docker container includes:
|
||||||
- ✅ **Backend**: Node.js + Express.js API
|
- **Backend**: Node.js + Express.js API
|
||||||
- ✅ **Frontend**: React web interface
|
- **Frontend**: React web interface (served by Express as static files)
|
||||||
- ✅ **Database**: MongoDB (embedded)
|
- **Database**: MongoDB (separate container, managed by Compose)
|
||||||
- ✅ **Cache**: Redis (embedded)
|
- **Cache**: Redis (separate container, managed by Compose)
|
||||||
- ✅ **Web Server**: Nginx reverse proxy
|
|
||||||
- ✅ **Process Manager**: Supervisor (manages all 5 services)
|
|
||||||
|
|
||||||
All in **ONE container** on port **3579**
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Unraid server with Docker support
|
- Unraid server with Docker support
|
||||||
- ~2GB RAM available
|
- Docker Compose (or Unraid's Compose Manager plugin)
|
||||||
- ~20GB storage for database/media
|
|
||||||
- Internet connection
|
|
||||||
|
|
||||||
## Installation Steps
|
## Installation
|
||||||
|
|
||||||
### Step 1: Clone Repository
|
### Step 1: Clone Repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Via terminal on Unraid
|
cd /mnt/user/appdata
|
||||||
cd /mnt/user/appdata # or your preferred path
|
|
||||||
git clone https://github.com/R0m1k3/podcastic.git
|
git clone https://github.com/R0m1k3/podcastic.git
|
||||||
cd podcastic
|
cd podcastic
|
||||||
```
|
```
|
||||||
|
|
||||||
Or download as ZIP from GitHub and extract.
|
|
||||||
|
|
||||||
### Step 2: Configure Environment
|
### Step 2: Configure Environment
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Copy template
|
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
# Edit with your settings
|
|
||||||
nano .env
|
nano .env
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key settings**:
|
**Change at minimum:**
|
||||||
```env
|
```env
|
||||||
NODE_ENV=production
|
JWT_SECRET=your-random-secret-here
|
||||||
|
|
||||||
# JWT Secret - CHANGE THIS!
|
|
||||||
JWT_SECRET=your-super-secret-key-change-me-in-production
|
|
||||||
|
|
||||||
# Optional: PodcastIndex API for podcast search
|
|
||||||
PODCAST_INDEX_API_KEY=
|
|
||||||
PODCAST_INDEX_API_SECRET=
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Build Docker Image
|
### Step 3: Build and Start
|
||||||
|
|
||||||
Option A: Build locally (takes ~5-10 minutes)
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.unraid.yml build --no-cache
|
docker-compose build --no-cache
|
||||||
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Option B: Use pre-built image (faster)
|
### Step 4: Access
|
||||||
```bash
|
|
||||||
# Will be available after first push to Docker Hub
|
Open: `http://your-unraid-ip:3579`
|
||||||
# docker pull r0m1k3/podcastic:latest
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Container Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose services:
|
||||||
|
├── podcastic (port 3579)
|
||||||
|
│ ├── Express.js API → /api/*
|
||||||
|
│ └── React frontend → /* (static files)
|
||||||
|
├── podcastic-mongodb → internal :27017
|
||||||
|
└── podcastic-redis → internal :6379
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Start Container
|
Only port **3579** is exposed externally.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Persistence
|
||||||
|
|
||||||
|
Add volume mounts to `docker-compose.yml` for persistence:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
mongodb:
|
||||||
|
volumes:
|
||||||
|
- /mnt/user/appdata/podcastic/db:/data/db
|
||||||
|
|
||||||
|
redis:
|
||||||
|
volumes:
|
||||||
|
- /mnt/user/appdata/podcastic/redis:/data
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Management Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start
|
|
||||||
docker-compose -f docker-compose.unraid.yml up -d
|
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
docker-compose -f docker-compose.unraid.yml logs -f podcastic
|
docker-compose logs -f app
|
||||||
|
|
||||||
# Check status
|
# Restart
|
||||||
docker ps | grep podcastic
|
docker-compose restart app
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Access Application
|
# Stop all
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
Open browser:
|
# Update
|
||||||
```
|
|
||||||
http://your-unraid-ip:3579
|
|
||||||
```
|
|
||||||
|
|
||||||
**First Time Setup**:
|
|
||||||
1. Click "Create Account"
|
|
||||||
2. Register with email/password
|
|
||||||
3. Log in
|
|
||||||
4. Go to "Discover" to search podcasts
|
|
||||||
5. Subscribe and listen!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Container Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Single Container (podcastic)
|
|
||||||
├── Nginx (port 3579)
|
|
||||||
│ ├── Serves static frontend
|
|
||||||
│ └── Proxies /api to backend
|
|
||||||
├── Node.js Backend (internal port 5000)
|
|
||||||
│ ├── API routes
|
|
||||||
│ ├── Authentication
|
|
||||||
│ └── Database management
|
|
||||||
├── MongoDB (internal port 27017)
|
|
||||||
│ └── Podcast data storage
|
|
||||||
└── Redis (internal port 6379)
|
|
||||||
└── Cache & sessions
|
|
||||||
```
|
|
||||||
|
|
||||||
All services communicate **internally** - only port 3579 exposed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Unraid Integration
|
|
||||||
|
|
||||||
### Add to Unraid via WebUI
|
|
||||||
|
|
||||||
1. **Settings → Docker** → Enable Docker
|
|
||||||
2. **Docker** tab
|
|
||||||
3. Add custom template:
|
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Container version="2">
|
|
||||||
<Name>podcastic</Name>
|
|
||||||
<Repository>r0m1k3/podcastic:latest</Repository>
|
|
||||||
<Registry>https://hub.docker.com/</Registry>
|
|
||||||
<Network>bridge</Network>
|
|
||||||
<MyIP/>
|
|
||||||
<Shell>sh</Shell>
|
|
||||||
<Privileged>false</Privileged>
|
|
||||||
<Support/>
|
|
||||||
<Project/>
|
|
||||||
<Overview/>
|
|
||||||
<Category/>
|
|
||||||
<WebUI>http://[IP]:[PORT:3579]</WebUI>
|
|
||||||
<TemplateURL/>
|
|
||||||
<Icon/>
|
|
||||||
<ExtraParams/>
|
|
||||||
<PostArgs/>
|
|
||||||
<StaticNetwork>false</StaticNetwork>
|
|
||||||
<DonateText/>
|
|
||||||
<DonateLink/>
|
|
||||||
<Description>Podcastic - Multi-user podcast streaming application</Description>
|
|
||||||
<Networking>
|
|
||||||
<Mode>bridge</Mode>
|
|
||||||
<Publish>
|
|
||||||
<Port>
|
|
||||||
<HostPort>3579</HostPort>
|
|
||||||
<ContainerPort>3579</ContainerPort>
|
|
||||||
<Protocol>tcp</Protocol>
|
|
||||||
</Port>
|
|
||||||
</Publish>
|
|
||||||
</Networking>
|
|
||||||
<Data>
|
|
||||||
<Volume>
|
|
||||||
<HostDir>/mnt/user/appdata/podcastic/db</HostDir>
|
|
||||||
<ContainerDir>/data/db</ContainerDir>
|
|
||||||
<Mode>rw</Mode>
|
|
||||||
</Volume>
|
|
||||||
<Volume>
|
|
||||||
<HostDir>/mnt/user/appdata/podcastic/redis</HostDir>
|
|
||||||
<ContainerDir>/data/redis</ContainerDir>
|
|
||||||
<Mode>rw</Mode>
|
|
||||||
</Volume>
|
|
||||||
<Volume>
|
|
||||||
<HostDir>/mnt/user/appdata/podcastic/logs</HostDir>
|
|
||||||
<ContainerDir>/var/log/supervisor</ContainerDir>
|
|
||||||
<Mode>rw</Mode>
|
|
||||||
</Volume>
|
|
||||||
</Data>
|
|
||||||
<Environment>
|
|
||||||
<Variable>
|
|
||||||
<Name>JWT_SECRET</Name>
|
|
||||||
<Value>change-me-in-production</Value>
|
|
||||||
</Variable>
|
|
||||||
<Variable>
|
|
||||||
<Name>PODCAST_INDEX_API_KEY</Name>
|
|
||||||
<Value/>
|
|
||||||
</Variable>
|
|
||||||
<Variable>
|
|
||||||
<Name>PODCAST_INDEX_API_SECRET</Name>
|
|
||||||
<Value/>
|
|
||||||
</Variable>
|
|
||||||
</Environment>
|
|
||||||
</Container>
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use Docker Compose tab with `docker-compose.unraid.yml`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📂 Data Persistence
|
|
||||||
|
|
||||||
Data is saved to Unraid shares:
|
|
||||||
|
|
||||||
```
|
|
||||||
/mnt/user/appdata/podcastic/
|
|
||||||
├── db/ # MongoDB data (podcasts, users, episodes)
|
|
||||||
├── redis/ # Redis cache data
|
|
||||||
└── logs/ # Application logs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup database
|
|
||||||
docker exec podcastic mongodump --out /backup/mongodb
|
|
||||||
|
|
||||||
# Copy to safe location
|
|
||||||
cp -r /mnt/user/appdata/podcastic /mnt/backup/podcastic-backup
|
|
||||||
```
|
|
||||||
|
|
||||||
### Restore
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec podcastic mongorestore /backup/mongodb
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Management Commands
|
|
||||||
|
|
||||||
### View Logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# All logs
|
|
||||||
docker logs -f podcastic
|
|
||||||
|
|
||||||
# Specific service
|
|
||||||
docker exec podcastic tail -f /var/log/supervisor/backend.out.log
|
|
||||||
docker exec podcastic tail -f /var/log/supervisor/mongodb.out.log
|
|
||||||
docker exec podcastic tail -f /var/log/supervisor/redis.out.log
|
|
||||||
docker exec podcastic tail -f /var/log/supervisor/nginx.out.log
|
|
||||||
```
|
|
||||||
|
|
||||||
### Restart Container
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose -f docker-compose.unraid.yml restart podcastic
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stop Container
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose -f docker-compose.unraid.yml down
|
|
||||||
|
|
||||||
# Keep volumes (data persists)
|
|
||||||
# Data in /mnt/user/appdata/podcastic/ is preserved
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update to Latest
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pull latest code
|
|
||||||
cd /path/to/podcastic
|
|
||||||
git pull
|
git pull
|
||||||
|
docker-compose build --no-cache
|
||||||
# Rebuild and restart
|
docker-compose up -d
|
||||||
docker-compose -f docker-compose.unraid.yml build --no-cache
|
|
||||||
docker-compose -f docker-compose.unraid.yml up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
## Unraid WebUI Template
|
||||||
|
|
||||||
### Container won't start
|
Add via Docker > Add Container:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Repository | (build from compose) |
|
||||||
|
| Port | 3579:3579 |
|
||||||
|
| Variable: JWT_SECRET | your-secret |
|
||||||
|
| Variable: MONGODB_URI | mongodb://podcastic-mongodb:27017/podcastic |
|
||||||
|
| Variable: REDIS_URL | redis://podcastic-redis:6379 |
|
||||||
|
|
||||||
|
Or use **Compose Manager plugin** with the `docker-compose.yml` from this repo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check logs
|
# Check container status
|
||||||
docker logs podcastic
|
docker-compose ps
|
||||||
|
|
||||||
# Common issues:
|
# Check health endpoint
|
||||||
# - Port 3579 in use: Change in docker-compose.unraid.yml
|
|
||||||
# - Insufficient RAM: Free up memory
|
|
||||||
# - Permission issues: Check volume permissions
|
|
||||||
```
|
|
||||||
|
|
||||||
### MongoDB not starting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check mongo logs
|
|
||||||
docker exec podcastic tail -f /var/log/supervisor/mongodb.out.log
|
|
||||||
|
|
||||||
# Repair database
|
|
||||||
docker exec podcastic mongod --repair --dbpath /data/db
|
|
||||||
```
|
|
||||||
|
|
||||||
### Can't access on port 3579
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check if container is running
|
|
||||||
docker ps | grep podcastic
|
|
||||||
|
|
||||||
# Check if port is open
|
|
||||||
netstat -tulpn | grep 3579
|
|
||||||
|
|
||||||
# Test connection
|
|
||||||
curl http://localhost:3579/health
|
curl http://localhost:3579/health
|
||||||
|
|
||||||
|
# Check API
|
||||||
|
curl http://localhost:3579/api/health
|
||||||
```
|
```
|
||||||
|
|
||||||
### Out of disk space
|
|
||||||
|
|
||||||
MongoDB can grow large. Check:
|
|
||||||
```bash
|
|
||||||
docker exec podcastic du -sh /data/db
|
|
||||||
|
|
||||||
# Clean old data (backup first!)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 Performance Optimization
|
|
||||||
|
|
||||||
### For Limited Resources
|
|
||||||
|
|
||||||
Edit `docker-compose.unraid.yml`:
|
|
||||||
```yaml
|
|
||||||
# Limit memory usage
|
|
||||||
mem_limit: 1g
|
|
||||||
cpus: '0.5'
|
|
||||||
```
|
|
||||||
|
|
||||||
### For High Performance
|
|
||||||
|
|
||||||
Increase limits:
|
|
||||||
```yaml
|
|
||||||
mem_limit: 4g
|
|
||||||
cpus: '2'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Disable Unneeded Features
|
|
||||||
|
|
||||||
If only streaming (no search):
|
|
||||||
```env
|
|
||||||
PODCAST_INDEX_API_KEY= # Leave empty
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Security Notes
|
|
||||||
|
|
||||||
### Change Default Secrets
|
|
||||||
|
|
||||||
```env
|
|
||||||
# MUST change in production!
|
|
||||||
JWT_SECRET=your-unique-secret-key-here
|
|
||||||
|
|
||||||
# Use strong password for first admin account
|
|
||||||
```
|
|
||||||
|
|
||||||
### Firewall Rules
|
|
||||||
|
|
||||||
Allow only trusted IPs:
|
|
||||||
```bash
|
|
||||||
# On Unraid, set firewall rules in UI
|
|
||||||
# Or use nginx config to restrict access
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSL/HTTPS Setup
|
|
||||||
|
|
||||||
If exposing to internet, use reverse proxy with SSL:
|
|
||||||
- Nginx Proxy Manager (separate container)
|
|
||||||
- Traefik (with Let's Encrypt)
|
|
||||||
- Unraid built-in reverse proxy
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Monitoring
|
|
||||||
|
|
||||||
### Check Container Health
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Real-time stats
|
|
||||||
docker stats podcastic
|
|
||||||
|
|
||||||
# Service status
|
|
||||||
docker exec podcastic supervisorctl status
|
|
||||||
|
|
||||||
# Expected output:
|
|
||||||
# mongodb RUNNING pid 123, uptime 0:00:45
|
|
||||||
# redis RUNNING pid 124, uptime 0:00:45
|
|
||||||
# backend RUNNING pid 125, uptime 0:00:45
|
|
||||||
# nginx RUNNING pid 126, uptime 0:00:45
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resource Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Disk usage
|
|
||||||
du -sh /mnt/user/appdata/podcastic/
|
|
||||||
|
|
||||||
# Memory usage
|
|
||||||
docker exec podcastic free -h
|
|
||||||
|
|
||||||
# CPU usage
|
|
||||||
docker stats --no-stream podcastic
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🆘 Support
|
|
||||||
|
|
||||||
If issues occur:
|
|
||||||
|
|
||||||
1. **Check logs**:
|
|
||||||
```bash
|
|
||||||
docker logs podcastic
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Check service status**:
|
|
||||||
```bash
|
|
||||||
docker exec podcastic supervisorctl status
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Rebuild container**:
|
|
||||||
```bash
|
|
||||||
docker-compose -f docker-compose.unraid.yml build --no-cache
|
|
||||||
docker-compose -f docker-compose.unraid.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Factory reset** (loses all data!):
|
|
||||||
```bash
|
|
||||||
docker-compose -f docker-compose.unraid.yml down -v
|
|
||||||
rm -rf /mnt/user/appdata/podcastic
|
|
||||||
# Start fresh
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Next Steps
|
|
||||||
|
|
||||||
1. **First Run**: Create account, set JWT_SECRET properly
|
|
||||||
2. **Configure PodcastIndex**: Optional, enables podcast search
|
|
||||||
3. **Add Podcasts**: Via Discover or RSS URLs
|
|
||||||
4. **Enjoy**: Start listening!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Additional Resources
|
|
||||||
|
|
||||||
- **Main README**: See README.md
|
|
||||||
- **Getting Started**: See GETTING_STARTED.md
|
|
||||||
- **PodcastIndex Setup**: See PODCAST_INDEX_SETUP.md
|
|
||||||
- **GitHub**: https://github.com/R0m1k3/podcastic
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Last Updated**: 2026-04-16
|
**Last Updated**: 2026-04-16
|
||||||
**Tested on**: Unraid 6.12+
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
FROM node:20-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
COPY tsconfig.json ./
|
|
||||||
COPY src ./src
|
|
||||||
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
CMD ["node", "dist/index.js"]
|
|
||||||
+19
-4
@@ -1,14 +1,18 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { connectDB } from './config/database';
|
import { connectDB } from './config/database';
|
||||||
import { initRedis, closeRedis } from './config/redis';
|
import { initRedis, closeRedis } from './config/redis';
|
||||||
import { authenticate, optional } from './middleware/auth';
|
import { errorHandler } from './middleware/errorHandler';
|
||||||
import { errorHandler, notFoundHandler } from './middleware/errorHandler';
|
|
||||||
import authRoutes from './routes/authRoutes';
|
import authRoutes from './routes/authRoutes';
|
||||||
import podcastRoutes from './routes/podcastRoutes';
|
import podcastRoutes from './routes/podcastRoutes';
|
||||||
import episodeRoutes from './routes/episodeRoutes';
|
import episodeRoutes from './routes/episodeRoutes';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 5000;
|
const PORT = process.env.PORT || 5000;
|
||||||
|
|
||||||
@@ -48,8 +52,19 @@ app.use('/api/auth', authRoutes);
|
|||||||
app.use('/api/podcasts', podcastRoutes);
|
app.use('/api/podcasts', podcastRoutes);
|
||||||
app.use('/api/episodes', episodeRoutes);
|
app.use('/api/episodes', episodeRoutes);
|
||||||
|
|
||||||
// 404 handler
|
// Serve frontend static files in production
|
||||||
app.use(notFoundHandler);
|
const frontendDist = path.join(__dirname, '..', 'public');
|
||||||
|
app.use(express.static(frontendDist));
|
||||||
|
|
||||||
|
// SPA fallback — return index.html for any non-API route
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
const indexFile = path.join(frontendDist, 'index.html');
|
||||||
|
res.sendFile(indexFile, (err) => {
|
||||||
|
if (err) {
|
||||||
|
res.status(404).json({ success: false, message: 'Not found' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Error handler (must be last)
|
// Error handler (must be last)
|
||||||
app.use(errorHandler);
|
app.use(errorHandler);
|
||||||
|
|||||||
+5
-17
@@ -19,16 +19,17 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- podcastic
|
- podcastic
|
||||||
|
|
||||||
backend:
|
app:
|
||||||
build:
|
build:
|
||||||
context: ./backend
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: podcastic-backend
|
container_name: podcastic
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "3579:3579"
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
|
PORT: 3579
|
||||||
MONGODB_URI: mongodb://mongodb:27017/podcastic
|
MONGODB_URI: mongodb://mongodb:27017/podcastic
|
||||||
REDIS_URL: redis://redis:6379
|
REDIS_URL: redis://redis:6379
|
||||||
JWT_SECRET: ${JWT_SECRET:-change_me_in_production}
|
JWT_SECRET: ${JWT_SECRET:-change_me_in_production}
|
||||||
@@ -42,19 +43,6 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- podcastic
|
- podcastic
|
||||||
|
|
||||||
frontend:
|
|
||||||
build:
|
|
||||||
context: ./frontend
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: podcastic-frontend
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "3579:3579"
|
|
||||||
depends_on:
|
|
||||||
- backend
|
|
||||||
networks:
|
|
||||||
- podcastic
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mongodb_data:
|
mongodb_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
FROM node:20-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
||||||
|
|
||||||
COPY <<'EOF' /etc/nginx/conf.d/default.conf
|
|
||||||
server {
|
|
||||||
listen 3579;
|
|
||||||
server_name _;
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api {
|
|
||||||
proxy_pass http://backend:5000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
EXPOSE 3579
|
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api';
|
const API_URL = import.meta.env.VITE_API_URL || '/api';
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export default defineConfig({
|
|||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:5000',
|
target: 'http://localhost:5000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user