|
| 1 | +# DigitalOcean Deployment Assessment and Guide |
| 2 | + |
| 3 | +## Feasibility Assessment (No Code Changes) |
| 4 | + |
| 5 | +**Short answer: Yes, deployment is feasible without additional code changes.** |
| 6 | + |
| 7 | +This repository already includes: |
| 8 | + |
| 9 | +- A multi-service Docker Compose setup (`postgres`, `backend`, `frontend`). |
| 10 | +- Production-capable Dockerfiles for both backend and frontend. |
| 11 | +- Backend migration-on-start behavior. |
| 12 | +- Runtime configuration via environment variables. |
| 13 | + |
| 14 | +### Important caveats to handle at deployment time |
| 15 | + |
| 16 | +1. **Set production environment variables explicitly.** |
| 17 | + - You must override defaults for `JWT_SECRET`, `CORS_ORIGIN`, and API URLs. |
| 18 | +2. **Frontend API URL is build-time.** |
| 19 | + - `VITE_API_URL` is compiled into frontend assets during image build. |
| 20 | +3. **Backend CORS must include your public frontend origin.** |
| 21 | + - Set `CORS_ORIGIN=https://your-domain.com` (or comma-separated list). |
| 22 | +4. **Migration startup behavior exists.** |
| 23 | + - Backend runs `prisma migrate deploy` at startup; ensure DB credentials and migration history are correct before first production run. |
| 24 | +5. **Use a reverse proxy + TLS in front of services.** |
| 25 | + - Expose only 80/443 publicly and proxy to frontend/backend containers. |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## Architecture Recommendation for a Droplet |
| 30 | + |
| 31 | +- **Droplet**: Ubuntu 22.04+. |
| 32 | +- **Services**: Docker Compose stack from repo. |
| 33 | +- **Reverse proxy**: Nginx on host (or Caddy) for TLS termination. |
| 34 | +- **Domain**: `app.example.com` for frontend and either: |
| 35 | + - path-based API (`https://app.example.com/api`), or |
| 36 | + - subdomain API (`https://api.example.com`). |
| 37 | +- **Database**: |
| 38 | + - Option A: Compose PostgreSQL container (simple/single-host). |
| 39 | + - Option B: Managed PostgreSQL (more resilient, recommended for production). |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +## Step-by-Step Deployment (Docker Compose on Droplet) |
| 44 | + |
| 45 | +## 1) Prepare DigitalOcean |
| 46 | + |
| 47 | +1. Create a droplet (Ubuntu 22.04+), assign static IP. |
| 48 | +2. Add DNS records: |
| 49 | + - `A app.example.com -> <droplet-ip>` |
| 50 | + - Optional `A api.example.com -> <droplet-ip>` |
| 51 | +3. SSH into droplet as sudo user. |
| 52 | + |
| 53 | +## 2) Install Docker Engine + Compose plugin |
| 54 | + |
| 55 | +```bash |
| 56 | +sudo apt update |
| 57 | +sudo apt install -y ca-certificates curl gnupg |
| 58 | +sudo install -m 0755 -d /etc/apt/keyrings |
| 59 | +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg |
| 60 | +sudo chmod a+r /etc/apt/keyrings/docker.gpg |
| 61 | +echo \ |
| 62 | + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ |
| 63 | + $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null |
| 64 | +sudo apt update |
| 65 | +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin |
| 66 | +sudo usermod -aG docker $USER |
| 67 | +# The following command applies the new group membership to the current shell session. |
| 68 | +# For the change to be permanent across all sessions, you need to log out and log back in. |
| 69 | +newgrp docker |
| 70 | +``` |
| 71 | + |
| 72 | +## 3) Clone repo and prepare deployment files |
| 73 | + |
| 74 | +```bash |
| 75 | +git clone <your-repo-url> TaskMan |
| 76 | +cd TaskMan |
| 77 | +``` |
| 78 | + |
| 79 | +Create a production override file (do not edit base compose if you want to keep local defaults). |
| 80 | + |
| 81 | +`docker-compose.prod.yml`: |
| 82 | + |
| 83 | +```yaml |
| 84 | +services: |
| 85 | + backend: |
| 86 | + environment: |
| 87 | + NODE_ENV: production |
| 88 | + PORT: 4000 |
| 89 | + DATABASE_URL: postgresql://taskapp:${POSTGRES_PASSWORD}@postgres:5432/taskapp?schema=public |
| 90 | + JWT_SECRET: ${JWT_SECRET} |
| 91 | + JWT_EXPIRES_IN: 7d |
| 92 | + CORS_ORIGIN: https://app.example.com |
| 93 | + restart: unless-stopped |
| 94 | + |
| 95 | + frontend: |
| 96 | + build: |
| 97 | + context: ./frontend |
| 98 | + dockerfile: Dockerfile |
| 99 | + args: |
| 100 | + VITE_API_URL: https://app.example.com/api |
| 101 | + environment: |
| 102 | + PORT: 3000 |
| 103 | + restart: unless-stopped |
| 104 | + |
| 105 | + postgres: |
| 106 | + environment: |
| 107 | + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} |
| 108 | + restart: unless-stopped |
| 109 | +``` |
| 110 | +
|
| 111 | +Create `.env.prod`: |
| 112 | + |
| 113 | +```dotenv |
| 114 | +POSTGRES_PASSWORD=<strong-random-password> |
| 115 | +JWT_SECRET=<long-random-secret> |
| 116 | +``` |
| 117 | + |
| 118 | +## 4) Start stack |
| 119 | + |
| 120 | +```bash |
| 121 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d --build |
| 122 | +``` |
| 123 | + |
| 124 | +Check health/logs: |
| 125 | + |
| 126 | +```bash |
| 127 | +docker compose ps |
| 128 | +docker compose logs backend --tail=200 |
| 129 | +docker compose logs frontend --tail=200 |
| 130 | +``` |
| 131 | + |
| 132 | +Backend health endpoint: |
| 133 | + |
| 134 | +```bash |
| 135 | +curl -f http://127.0.0.1:4000/health |
| 136 | +``` |
| 137 | + |
| 138 | +## 5) Install and configure Nginx with Let's Encrypt |
| 139 | + |
| 140 | +Install: |
| 141 | + |
| 142 | +```bash |
| 143 | +sudo apt install -y nginx certbot python3-certbot-nginx |
| 144 | +``` |
| 145 | + |
| 146 | +Example Nginx server block for `app.example.com` (`/etc/nginx/sites-available/taskman`): |
| 147 | + |
| 148 | +```nginx |
| 149 | +server { |
| 150 | + listen 80; |
| 151 | + server_name app.example.com; |
| 152 | +
|
| 153 | + location / { |
| 154 | + proxy_pass http://127.0.0.1:3000; |
| 155 | + proxy_set_header Host $host; |
| 156 | + proxy_set_header X-Real-IP $remote_addr; |
| 157 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 158 | + proxy_set_header X-Forwarded-Proto $scheme; |
| 159 | + } |
| 160 | +
|
| 161 | + location /api/ { |
| 162 | + proxy_pass http://127.0.0.1:4000/; |
| 163 | + proxy_set_header Host $host; |
| 164 | + proxy_set_header X-Real-IP $remote_addr; |
| 165 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 166 | + proxy_set_header X-Forwarded-Proto $scheme; |
| 167 | + } |
| 168 | +
|
| 169 | + location /socket.io/ { |
| 170 | + proxy_pass http://127.0.0.1:4000/socket.io/; |
| 171 | + proxy_http_version 1.1; |
| 172 | + proxy_set_header Upgrade $http_upgrade; |
| 173 | + proxy_set_header Connection "upgrade"; |
| 174 | + proxy_set_header Host $host; |
| 175 | + proxy_set_header X-Real-IP $remote_addr; |
| 176 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 177 | + proxy_set_header X-Forwarded-Proto $scheme; |
| 178 | + } |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +Enable and test: |
| 183 | + |
| 184 | +```bash |
| 185 | +sudo ln -s /etc/nginx/sites-available/taskman /etc/nginx/sites-enabled/taskman |
| 186 | +sudo nginx -t |
| 187 | +sudo systemctl reload nginx |
| 188 | +``` |
| 189 | + |
| 190 | +Issue TLS cert: |
| 191 | + |
| 192 | +```bash |
| 193 | +sudo certbot --nginx -d app.example.com |
| 194 | +``` |
| 195 | + |
| 196 | +## 6) Lock down public exposure |
| 197 | + |
| 198 | +- Allow only `22`, `80`, `443` in cloud firewall / UFW. |
| 199 | +- Keep `3000`, `4000`, `5432` closed publicly. |
| 200 | + |
| 201 | +Example with UFW: |
| 202 | + |
| 203 | +```bash |
| 204 | +sudo ufw allow OpenSSH |
| 205 | +sudo ufw allow 80/tcp |
| 206 | +sudo ufw allow 443/tcp |
| 207 | +sudo ufw enable |
| 208 | +``` |
| 209 | + |
| 210 | +## 7) Validate production behavior |
| 211 | + |
| 212 | +1. Open `https://app.example.com`. |
| 213 | +2. Register/login flow works. |
| 214 | +3. Task CRUD works. |
| 215 | +4. Realtime updates (socket) work. |
| 216 | +5. Backend docs/health reachable through proxy path if intended. |
| 217 | + |
| 218 | +## 8) Updates / redeploy |
| 219 | + |
| 220 | +```bash |
| 221 | +cd ~/TaskMan |
| 222 | +git pull |
| 223 | +docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d --build |
| 224 | +``` |
| 225 | + |
| 226 | +--- |
| 227 | + |
| 228 | +## Operational Checklist |
| 229 | + |
| 230 | +- [ ] Strong secrets in `.env.prod`. |
| 231 | +- [ ] Correct frontend URL compiled (`VITE_API_URL`). |
| 232 | +- [ ] `CORS_ORIGIN` matches public frontend origin(s). |
| 233 | +- [ ] Backups configured (DB volume snapshots or managed DB backups). |
| 234 | +- [ ] HTTPS enabled and auto-renew working (`systemctl status certbot.timer`). |
| 235 | +- [ ] Container restart policy enabled. |
| 236 | +- [ ] Logs monitored (`docker compose logs -f`). |
| 237 | + |
| 238 | +--- |
| 239 | + |
| 240 | +## Feasibility Conclusion |
| 241 | + |
| 242 | +Deploying this codebase to a DigitalOcean droplet is **feasible today without code changes**, provided you supply production env values, run with Docker Compose overrides, and place Nginx + TLS in front of the containers. |
0 commit comments