diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3b74f85 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,27 @@ +# Dependencies +node_modules +backend/node_modules + +# Environment secrets - JANGAN masuk ke image +backend/.env +.env + +# Log files +*.log +backend/*.log + +# Git files +.git +.gitignore + +# OS files +.DS_Store +Thumbs.db + +# Editor +.vscode +.idea + +# Temporary files +*.tmp +*.swp diff --git a/.gitignore b/.gitignore index 728ff4f..bc7b2e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,13 @@ node_modules/ +backend/node_modules/ backend/.env *.log +backend/*.log + +# OS +.DS_Store +Thumbs.db + +# Editor +.vscode/ +.idea/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..add13b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# ─── Stage 1: Install dependencies ────────────────────────────────────────── +FROM node:22-alpine AS deps +WORKDIR /app +COPY backend/package*.json ./ +RUN npm ci --omit=dev + +# ─── Stage 2: Runtime ──────────────────────────────────────────────────────── +FROM node:22-alpine AS runner +WORKDIR /app + +# Install dumb-init untuk proper signal handling +RUN apk add --no-cache dumb-init + +# Copy dependencies dari stage deps +COPY --from=deps /app/node_modules ./node_modules + +# Copy backend source +COPY backend/ ./backend/ + +# Copy frontend static files +COPY frontend/ ./frontend/ + +# Set working directory ke backend (karena server.js ada di sini) +WORKDIR /app/backend + +# Non-root user untuk keamanan +RUN addgroup -S appgroup && adduser -S appuser -G appgroup +USER appuser + +EXPOSE 3000 + +ENV NODE_ENV=production +ENV PORT=3000 + +# Health check (dipakai Coolify untuk monitoring) +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD wget -qO- http://localhost:3000/health || exit 1 + +ENTRYPOINT ["dumb-init", "--"] +CMD ["node", "server.js"] diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..caac3b3 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,11 @@ +# ─── Konfigurasi Database MySQL ───────────────────────────────────────────── +# Di Coolify: gunakan nama service MySQL yang dibuat, contoh: "mysql" atau "db" +DB_HOST=db +DB_PORT=3306 +DB_USER=webgis_user +DB_PASSWORD=ganti_dengan_password_kuat +DB_NAME=spbu_pontianak + +# ─── Server ────────────────────────────────────────────────────────────────── +PORT=3000 +NODE_ENV=production diff --git a/backend/server.js b/backend/server.js index a3dfb49..a16a8cb 100644 --- a/backend/server.js +++ b/backend/server.js @@ -10,9 +10,21 @@ const app = express(); const PORT = process.env.PORT || 3000; // ─── Middleware ─────────────────────────────────────────────────────────────── +app.set("trust proxy", 1); // Untuk Coolify reverse proxy (Traefik/Nginx) app.use(cors()); app.use(express.json()); app.use("/api/poverty", povertyRoutes); + +// ─── Health Check (dibutuhkan Coolify untuk monitoring container) ───────────────── +app.get("/health", async (req, res) => { + try { + await db.execute("SELECT 1"); + res.json({ status: "ok", db: "connected", timestamp: new Date().toISOString() }); + } catch (err) { + res.status(503).json({ status: "error", db: "disconnected", message: err.message }); + } +}); + // ─── Root: landing page (harus sebelum express.static) ─────────────────────── app.get("/", (req, res) => { res.sendFile(path.join(__dirname, "../frontend/landing.html")); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cf7b1b1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,61 @@ +version: "3.9" + +services: + # ─── Database MySQL ─────────────────────────────────────────────────────── + db: + image: mysql:8.0 + container_name: webgis_db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + MYSQL_DATABASE: ${DB_NAME:-spbu_pontianak} + MYSQL_USER: ${DB_USER:-webgis_user} + MYSQL_PASSWORD: ${DB_PASSWORD:-webgispassword} + volumes: + - db_data:/var/lib/mysql + - ./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + networks: + - webgis_net + + # ─── Aplikasi Node.js ───────────────────────────────────────────────────── + app: + build: + context: . + dockerfile: Dockerfile + container_name: webgis_app + restart: unless-stopped + ports: + - "${APP_PORT:-3000}:3000" + environment: + NODE_ENV: production + PORT: 3000 + DB_HOST: db + DB_PORT: 3306 + DB_USER: ${DB_USER:-webgis_user} + DB_PASSWORD: ${DB_PASSWORD:-webgispassword} + DB_NAME: ${DB_NAME:-spbu_pontianak} + depends_on: + db: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + networks: + - webgis_net + +volumes: + db_data: + driver: local + +networks: + webgis_net: + driver: bridge