-
Notifications
You must be signed in to change notification settings - Fork 0
Deployment
Complete deployment guide for Photo Map MVP - Docker Compose setup, Mikrus VPS deployment, SSL configuration, updates, and troubleshooting.
This guide describes the Docker Compose deployment strategy for Photo Map MVP on VPS hosting. The application runs in two containers (backend + frontend/nginx) with automatic SSL via provider proxy and shared PostgreSQL service.
Deployment Strategy:
- Method: Docker Compose (2 containers)
- Target: VPS hosting (4GB RAM recommended)
- SSL: Automatic (provider-managed wildcard domain)
- PostgreSQL: Shared service or dedicated instance
- Storage: Docker volume (photo-map-uploads)
Related Pages:
- Architecture - System architecture overview
- Development Setup - Local environment configuration
- Scripts Reference - Build and deployment scripts
- Prerequisites
- Deployment Architecture
- Preparing .env
- Build Images Locally
- Deploy to Mikrus VPS
- Deployment Verification
- Updates and Maintenance
- SSL Configuration (Automatic)
- Troubleshooting
- ✅ Docker 20+ (for building images)
- ✅ Docker Compose 2+ (optional - only for local testing)
- ✅ SSH client (for connecting to Mikrus VPS)
- ✅ Java 17 JDK + Maven (for building backend JAR)
- ✅ Node.js 18+ + Angular CLI (for building frontend)
- ✅ Docker + Docker Compose - installation via script
- ✅ SSH access - root access recommended
- ✅ PostgreSQL - Shared service or dedicated instance
- ✅ 4GB RAM - sufficient for Docker containers
- SSH host and port (e.g.,
yourserver.example.com, port22or custom) - PostgreSQL credentials (check your provider's panel)
- Assigned ports for web services (check provider documentation)
docker-compose.yml
├── backend (photo-map-backend:latest)
│ ├── Port: 8080 (internal)
│ ├── Volume: photo-map-uploads (persistence)
│ └── Env: .env (PostgreSQL, JWT, Admin)
│
└── frontend (photo-map-frontend:latest)
├── Port: <your-assigned-port> (external - provider proxy)
├── Nginx + Angular SPA
└── Proxy: /api → backend:8080
Internet (HTTPS)
↓
Provider Proxy (SSL termination - wildcard domain)
↓ HTTP
Frontend Container (nginx:80)
↓ HTTP (internal Docker network)
Backend Container (Spring Boot:8080)
↓
PostgreSQL (shared service or dedicated)
Important:
- Backend serves HTTP (port 8080 internal)
- Frontend nginx listens on assigned port (external, e.g., 30288 or your provider's port)
- Provider proxy adds SSL and redirects to
https://<your-domain>/ - Users connect via HTTPS, containers see HTTP
photo-map-uploads/
├── input/ # Drop zone (web uploads)
├── original/ # Full resolution
├── medium/ # 300px thumbnails
└── failed/ # Processing errors
Persistence: Volume /var/lib/docker/volumes/photo-map-uploads survives container restarts
cp deployment/.env.production.example deployment/.envLogin to Mikrus panel: https://mikr.us/panel/?a=postgres
Copy:
- Database name:
db_xxxxx - Username:
userxxxxx - Password:
********
Paste into deployment/.env:
# Spring Boot datasource configuration (REQUIRED)
DB_HOST=psql01.mikr.us
DB_PORT=5432
DB_NAME=db_xxxxx
DB_USERNAME=userxxxxx
DB_PASSWORD=********
# Legacy JDBC URL format (for reference only)
DATABASE_URL=jdbc:postgresql://psql01.mikr.us:5432/db_xxxxx
DATABASE_USERNAME=userxxxxx
DATABASE_PASSWORD=********Important: Spring Boot uses DB_* variables (not DATABASE_URL). Both versions are in .env for compatibility.
openssl rand -base64 32Paste the result into deployment/.env:
JWT_SECRET=xK8vN2pQr5tYw9zA1bCdE3fGhI4jKlM6nOpRqS7uVxY=Check assigned ports in Mikrus panel (format: 201xx, 301xx).
FRONTEND_PORT=30288ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=YourSecurePassword123!Important: Password must have minimum 8 characters (frontend validation).
./deployment/scripts/build-images.shScript performs:
- Build backend JAR:
./mvnw clean package -DskipTests - Build frontend:
ng build --configuration production - Build Docker image backend:
docker build -t photo-map-backend:latest backend/ - Build Docker image frontend:
docker build -t photo-map-frontend:latest frontend/
./mvnw clean package -DskipTestscd backend
docker build -t photo-map-backend:latest .
cd ..cd frontend
ng build --configuration production
cd ..cd frontend
docker build -t photo-map-frontend:latest .
cd ..docker images | grep photo-mapExpected output:
photo-map-backend latest abc123 2 minutes ago 300MB
photo-map-frontend latest def456 1 minute ago 50MB
If you have a deployment helper script configured (e.g., deployment/scripts/deploy-<yourhost>.sh):
# 1. Build Docker images locally
./deployment/scripts/build-images.sh
# 2. Deploy to your VPS
./deployment/scripts/deploy-<yourhost>.shFor initial setup with data reset:
./deployment/scripts/deploy-<yourhost>.sh --initWhat --init flag does:
⚠️ DANGER: Deletes ALL data (users, photos, ratings, files)- Resets database schema
- Recreates upload directories
- Admin user will be created on backend startup from remote
.env - Use ONLY for: initial production setup, development environment reset
Deployment script handles:
- ✅ SSH connection to VPS
- ✅ Docker image transfer (save/load)
- ✅ Docker Compose configuration
- ✅ Container start/restart
- ✅ Health checks
After deployment completes:
- Application will be available at your configured URL
- Check deployment script output for exact URL
Use manual steps if you don't have a deployment helper script.
# SSH to VPS
ssh root@<your-vps-host> -p <your-ssh-port>
# Install Docker + Docker Compose
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
# Verify installation
docker --version
docker compose version# Transfer docker-compose.yml + .env
scp -P <ssh-port> deployment/docker-compose.yml root@<your-vps-host>:/opt/photo-map/
scp -P <ssh-port> deployment/.env root@<your-vps-host>:/opt/photo-map/
# Verify
ssh root@<your-vps-host> -p <ssh-port> "ls -la /opt/photo-map/"Option A: Save/Load (recommended for first deployment)
# Save images locally
docker save photo-map-backend:latest | gzip > photo-map-backend.tar.gz
docker save photo-map-frontend:latest | gzip > photo-map-frontend.tar.gz
# Transfer to VPS
scp -P <ssh-port> photo-map-backend.tar.gz root@<your-vps-host>:/opt/photo-map/
scp -P <ssh-port> photo-map-frontend.tar.gz root@<your-vps-host>:/opt/photo-map/
# SSH to VPS and load images
ssh root@<your-vps-host> -p <ssh-port>
cd /opt/photo-map
docker load < photo-map-backend.tar.gz
docker load < photo-map-frontend.tar.gz
# Verify
docker images | grep photo-map
# Cleanup
rm photo-map-*.tar.gzOption B: Docker Registry (optional, for subsequent updates)
You can use Docker Hub or GitHub Container Registry for simpler updates.
# SSH to VPS (if not already connected)
ssh root@<your-vps-host> -p <ssh-port>
# Navigate to deployment directory
cd /opt/photo-map
# Start containers (detached mode)
docker compose up -d
# Check status
docker compose ps
# Expected output:
# NAME STATUS PORTS
# photo-map-backend Up 10 seconds 0.0.0.0:8080->8080/tcp
# photo-map-frontend Up 10 seconds 0.0.0.0:<your-port>->80/tcp# Backend logs
docker compose logs backend -f
# Expected output:
# Started PhotoMapApplication in X seconds
# Flyway migration completed successfully
# Frontend logs
docker compose logs frontend -f
# Ctrl+C to exit
# All logs
docker compose logs -f# SSH to VPS
ssh root@<your-vps-host> -p <ssh-port>
# Health check (internal)
curl http://localhost:8080/actuator/health
# Expected: {"status":"UP"}# Test HTTPS access (replace with your actual domain)
curl https://<your-domain>/
# Expected: Angular index.html# Test login endpoint (replace with your domain and actual password)
curl -X POST https://<your-domain>/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"your-password"}'
# Expected: {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}- Open browser:
https://<your-domain>/(replace with your actual domain) - Log in (admin credentials from remote
.env) - Navigate to
/gallery - Click "Upload Photos"
- Select a JPG/PNG photo with GPS EXIF
- Verify it appears in gallery and on map
# Check running containers
docker compose ps
# Check resource usage
docker stats
# Check volumes
docker volume ls | grep photo-map# 1. Build JAR + Docker image locally
./mvnw clean package -DskipTests
docker build -t photo-map-backend:latest backend/
# 2. Save image
docker save photo-map-backend:latest | gzip > photo-map-backend.tar.gz
# 3. Transfer to VPS
scp -P <ssh-port> photo-map-backend.tar.gz root@<your-vps-host>:/opt/photo-map/
# 4. SSH to VPS and update
ssh root@<your-vps-host> -p <ssh-port>
cd /opt/photo-map
docker load < photo-map-backend.tar.gz
docker compose up -d backend
# 5. Verify
docker compose logs backend -f# 1. Build Angular + Docker image locally
ng build --configuration production
docker build -t photo-map-frontend:latest frontend/
# 2. Save image
docker save photo-map-frontend:latest | gzip > photo-map-frontend.tar.gz
# 3. Transfer to VPS
scp -P <ssh-port> photo-map-frontend.tar.gz root@<your-vps-host>:/opt/photo-map/
# 4. SSH to VPS and update
ssh root@<your-vps-host> -p <ssh-port>
cd /opt/photo-map
docker load < photo-map-frontend.tar.gz
docker compose up -d frontend
# 5. Verify
curl https://<your-domain>/ssh root@<your-vps-host> -p <ssh-port>
cd /opt/photo-map
docker compose restartImportant: docker compose restart does NOT load new variables from .env. If you changed .env, use:
docker compose down && docker compose up -dAfter changing environment variables in deployment/.env:
# 1. Transfer updated .env to VPS
scp -P <ssh-port> deployment/.env root@<your-vps-host>:/opt/photo-map/
# 2. Recreate containers (restart is not enough!)
ssh root@<your-vps-host> -p <ssh-port>
cd /opt/photo-map
docker compose down
docker compose up -d
# 3. Verify new env variables
docker exec photo-map-backend env | grep YOUR_VARIABLEssh root@<your-vps-host> -p <ssh-port>
cd /opt/photo-map
docker compose downssh root@<your-vps-host> -p <ssh-port>
cd /opt/photo-map
# Stop and remove containers + volumes
docker compose down -v
# Start fresh
docker compose up -dUse ONLY for:
- Initial production setup
- Development environment reset
- Testing scenarios
What --init does:
- Resets database (TRUNCATE users, photos, ratings)
- Deletes all physical files from uploads/
- Resets settings to defaults
- Deploys application
- Admin user recreated automatically from remote
.env
Usage:
# If you have a deployment helper script:
./deployment/scripts/deploy-<yourhost>.sh --init
# Generic syntax (using deploy.sh directly):
./deployment/scripts/deploy.sh <srv_host> <ssh_port> --initSafety features:
- ✅ Requires interactive confirmation
- ✅ Production: Must type EXACT hostname
- ✅ Shows clear warning before execution
- ✅ Cannot be bypassed with flags
Example workflow:
# 1. Build images locally
./deployment/scripts/build-images.sh
# 2. Deploy with data reset (first deployment or reset scenario)
./deployment/scripts/deploy.sh <your-vps-host> <ssh-port> --init
# Output:
# ⚠️ WARNING: This will DELETE ALL DATA on <your-vps-host>
# To confirm, type the EXACT server hostname: <your-vps-host>
# > <your-vps-host>
#
# ✓ Confirmation accepted. Proceeding with data reset...
# Step INIT: Resetting data on remote server...
# ✓ Remote data reset completed
# Step 1: Checking Docker images...
# ...
# 3. Verify deployment
curl https://<your-domain>/Help:
# Show help for deployment script
./deployment/scripts/deploy.sh --help
# Or for your custom helper:
./deployment/scripts/deploy-<yourhost>.sh --helpStatus: ✅ Automatic - Most VPS providers (including Mikrus) offer SSL for shared domains
-
Domain format:
<server>-<port>.<provider-domain>(e.g.,srv01-30100.example.com) - Automatic SSL: Provider offers SSL certificate for wildcard domains
- Configuration: Zero - SSL works automatically
-
Access:
https://<your-subdomain>.<provider-domain>/(replace with your actual domain)
Ports configuration:
- Check your provider's documentation for assigned ports
- Shared domains usually require specific port ranges (not default port 80)
- Set
FRONTEND_PORTin.envto your assigned port
# Test HTTPS access (replace with your actual domain)
curl https://<your-domain>/
# Test SSL certificate (replace with your actual domain)
openssl s_client -connect <your-domain>:443 -servername <your-domain> < /dev/null | grep subject
# Expected: subject=CN=*.<provider-domain>Symptom:
docker compose ps
# photo-map-backend Exited (1)Diagnostic:
# Check logs
docker compose logs backend
# Common errors:
# - "Connection refused" → PostgreSQL credentials wrong
# - "Port 8080 already in use" → kill existing processSolutions:
A. PostgreSQL connection error
# Verify .env credentials
cat /opt/photo-map/.env | grep DATABASE
# Test PostgreSQL connection manually
psql -h psql01.mikr.us -U YOUR_USER -d YOUR_DB -p 5432B. Port conflict
# Check what uses port 8080
lsof -i :8080
# Kill process or change BACKEND_PORT in .envSymptom:
curl https://<your-domain>/
# 502 Bad GatewayDiagnostic:
# Check if backend is running
docker compose ps backend
# Expected: Up
# Test backend health directly
curl http://localhost:8080/actuator/healthSolutions:
A. Backend not running
docker compose up -d backend
docker compose logs backend -fB. Network issue
# Check Docker network
docker network ls | grep photo-map
# Recreate network
docker compose down
docker compose up -dSymptom:
- Upload returns 202 Accepted
- After 10-15 seconds photo doesn't appear in gallery
Diagnostic:
# Check backend logs
docker compose logs backend | grep -i upload
# Check volume
docker volume inspect photo-map-uploadsSolutions:
A. Volume permission issue
# Enter backend container
docker compose exec backend bash
# Check uploads directory
ls -la /app/uploads/Symptom:
docker compose logs backend
# OutOfMemoryError: Java heap spaceSolutions:
A. Increase heap size (backend Dockerfile)
ENTRYPOINT ["java", "-Xms256m", "-Xmx2048m", "-jar", "app.jar"]Rebuild image and redeploy.
# Status of all containers
docker compose ps
# Logs of all services
docker compose logs -f
# Backend logs
docker compose logs backend -f
# Resource usage
docker stats
# Enter backend container
docker compose exec backend bash
# Restart service
docker compose restart backend
# Remove all containers + volumes
docker compose down -v
# List volumes
docker volume ls
# Inspect volume
docker volume inspect photo-map-uploadsA: Yes, but Docker Compose is simpler and recommended for Mikrus (4GB RAM sufficient).
A:
- Backend image: ~300 MB (openjdk:17-jre-slim + JAR)
- Frontend image: ~50 MB (nginx:alpine + Angular build)
- Uploads volume: depends on number of photos (~5-8 MB/photo)
A: Nginx already has client_max_body_size 50M. If you need more:
- Edit
frontend/nginx.conf - Rebuild frontend image
- Redeploy
- Shared domain (SSL): https://wiki.mikr.us/wspoldzielona_domena
- IPv6: https://wiki.mikr.us/o_co_chodzi_z_ipv6
-
.ai/tech-stack.md- Deployment Stack specification -
.ai/features/feature-deployment-mikrus.md- Feature spec -
PROGRESS_TRACKER.md- Phase 6 tasks
- Docker Docs: https://docs.docker.com/
- Docker Compose: https://docs.docker.com/compose/
- Nginx: https://nginx.org/en/docs/
Last Updated: 2025-11-10
Source: deployment/README.md