Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions DOCKER_DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ The application uses a **single domain** for both frontend and backend:
- **Frontend Application:** https://localhost
- **Backend API:** https://localhost/api/
- **Django Admin:** https://localhost/admin/
- **Static Files:** https://localhost/static/ (Django admin and DRF assets)
- **Traefik Dashboard:** http://localhost:8080 (if enabled)

**Note:** Your browser will show a security warning because Traefik uses a default self-signed certificate. This is normal for local development. Click "Advanced" and "Proceed to localhost" to continue.
Expand Down Expand Up @@ -171,10 +172,13 @@ Let's Encrypt will automatically obtain and renew certificates.

The application uses a **single domain** for both frontend and backend:

- **Frontend:** Handles all requests to the root path and static assets
- **Backend:** Handles requests to `/api/*` and `/admin/*` paths
- **Frontend:** Handles all requests to the root path and Angular application assets
- **Backend:** Handles requests to `/api/*`, `/admin/*`, and `/static/*` paths
- `/api/*` - REST API endpoints
- `/admin/*` - Django admin interface
- `/static/*` - Django admin and DRF static files (CSS, JS, images)
- **Routing:** Managed by Traefik with path-based routing rules
- **Priority:** Backend routes have higher priority (100) to ensure API and admin paths are captured before frontend catch-all route (priority 1)
- **Priority:** Backend routes have higher priority (100) to ensure API, admin, and static paths are captured before frontend catch-all route (priority 1)

### Environment Variables

Expand Down
5 changes: 3 additions & 2 deletions DOCKER_QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ After deployment, the application uses a **single domain** for both frontend and
- **Frontend**: https://localhost
- **Backend API**: https://localhost/api/
- **Django Admin**: https://localhost/admin/
- **Static Files**: https://localhost/static/ (Django admin and DRF assets)
- **Traefik Dashboard**: http://localhost:8080 (if enabled)

**Note:** Browser will show a security warning for Traefik's default self-signed certificate. Click "Advanced" and proceed.
Expand Down Expand Up @@ -185,7 +186,7 @@ docker run --rm -v join_backend-data:/data alpine ls -la /data

### Frontend can't reach backend
1. Check API_URL in .env is `https://localhost/` (or your domain)
2. Verify backend is accessible at `/api` and `/admin` paths
2. Verify backend is accessible at `/api`, `/admin`, and `/static` paths
3. Check CORS settings in Django allow your frontend domain
4. Check Traefik routing: `docker compose logs traefik`

Expand Down Expand Up @@ -240,7 +241,7 @@ docker compose restart backend

## 💡 Tips

1. **Single Domain Architecture**: Both frontend and backend use the same domain - backend at `/api` and `/admin` paths
1. **Single Domain Architecture**: Both frontend and backend use the same domain - backend at `/api`, `/admin`, and `/static` paths
2. **Use PostgreSQL in Production**: SQLite is fine for development but use PostgreSQL for production
3. **Regular Backups**: Set up automated daily backups (database is persisted in Docker volume `backend-data`)
4. **Monitor Logs**: Regularly check logs for errors or suspicious activity
Expand Down
2 changes: 1 addition & 1 deletion backend/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/

STATIC_URL = 'static/'
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'

# Default primary key field type
Expand Down
1 change: 1 addition & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ case $option in
echo " Frontend: http://localhost"
echo " Backend API: http://localhost/api/"
echo " Django Admin: http://localhost/admin/"
echo " Static Files: http://localhost/static/"
echo " Traefik Dashboard: http://localhost:8080"
echo
echo "Next steps:"
Expand Down
34 changes: 34 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,40 @@ services:
retries: 3
start_period: 40s

# Static Server - Serves Django admin and DRF static files
static-server:
build:
context: ./static-server
dockerfile: Dockerfile
container_name: join-static-server
restart: unless-stopped
volumes:
- backend-static:/usr/share/nginx/html/static:ro
networks:
- web
labels:
- "traefik.enable=true"
# HTTP router - serves /static/ path
- "traefik.http.routers.static-server.rule=Host(`${DOMAIN:-localhost}`) && PathPrefix(`/static`)"
- "traefik.http.routers.static-server.entrypoints=web"
- "traefik.http.routers.static-server.priority=110"
- "traefik.http.services.static-server.loadbalancer.server.port=80"
# HTTPS router - uses Traefik default certificate (or Let's Encrypt if configured)
- "traefik.http.routers.static-server-secure.rule=Host(`${DOMAIN:-localhost}`) && PathPrefix(`/static`)"
- "traefik.http.routers.static-server-secure.entrypoints=websecure"
- "traefik.http.routers.static-server-secure.priority=110"
- "traefik.http.routers.static-server-secure.tls=true"
# Uncomment the next line to use Let's Encrypt instead of Traefik default certificate
# - "traefik.http.routers.static-server-secure.tls.certresolver=letsencrypt"
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
depends_on:
- backend

# Frontend - Angular Application
frontend:
build:
Expand Down
3 changes: 3 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ RUN npm run build
# Stage 2: Serve with nginx
FROM nginx:alpine

# Install wget for health checks
RUN apk add --no-cache wget

# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

Expand Down
19 changes: 19 additions & 0 deletions static-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Static Server Dockerfile - Serves Django admin and DRF static files
FROM nginx:alpine

# Install wget for health checks
RUN apk add --no-cache wget

# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Create directory for static files
RUN mkdir -p /usr/share/nginx/html/static

# The static files will be copied from the backend build stage
# This is done in docker-compose.yml using a shared volume

# Expose port
EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
40 changes: 40 additions & 0 deletions static-server/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;

# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

# Serve static files from /static path
location /static/ {
alias /usr/share/nginx/html/static/;
# Long cache since Django collectstatic includes content hash in filenames
expires 1y;
add_header Cache-Control "public, immutable";

# Handle missing files gracefully
try_files $uri $uri/ =404;
}

# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}

# Return 404 for all other paths
location / {
return 404;
}
}