feat: add docker-compose files for S3 and local filesystem storage options

Introduce three new docker-compose files: docker-compose-file-system.yaml for local filesystem storage, docker-compose-s3-minio.yaml for MinIO S3-compatible storage, and docker-compose-s3.yaml for direct S3 storage. Each configuration includes environment variables for service setup, health checks, and persistent volume management. This enhances deployment flexibility for the Palmr application.
This commit is contained in:
Daniel Luiz Alves
2025-05-28 18:07:16 -03:00
parent 8290ccaaa9
commit fff4675aa3
18 changed files with 453 additions and 244 deletions

View File

@@ -1,9 +0,0 @@
node_modules
dist
.env
.env.*
*.log
.git
.gitignore
.next
.cache

View File

@@ -1,15 +1,16 @@
DATABASE_URL="postgresql://palmr:palmr123@localhost:5432/palmr?schema=public"
FRONTEND_URL="http://localhost:3000"
PORT=3333
SERVER_IP="localhost"
MAX_FILESIZE="1073741824"
ENABLE_S3=false
ENCRYPTION_KEY=change-this-key-in-production-min-32-chars
S3_ENDPOINT=localhost
S3_PORT=9000
S3_USE_SSL=false
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_REGION=us-east-1
S3_BUCKET_NAME=palmr-files
S3_FORCE_PATH_STYLE=true
# For use with S3 compatible
# S3_ENDPOINT=localhost
# S3_PORT=9000
# S3_USE_SSL=false
# S3_ACCESS_KEY=minioadmin
# S3_SECRET_KEY=minioadmin
# S3_REGION=us-east-1
# S3_BUCKET_NAME=palmr-files
# S3_FORCE_PATH_STYLE=true

View File

@@ -2,3 +2,4 @@ node_modules
.env
dist/*
uploads/*
temp-chunks/*

View File

@@ -1,26 +0,0 @@
FROM node:22-alpine
WORKDIR /app/server
RUN apk add --no-cache netcat-openbsd
RUN npm install -g pnpm
COPY package*.json ./
COPY pnpm-lock.yaml ./
COPY prisma ./prisma/
COPY scripts ./scripts/
RUN rm -rf node_modules/.prisma
RUN pnpm install --frozen-lockfile
RUN npx prisma generate
COPY . .
RUN pnpm build
RUN chmod +x ./scripts/start.sh
EXPOSE 3333
CMD ["./scripts/start.sh"]

View File

@@ -0,0 +1,22 @@
services:
postgres:
image: bitnami/postgresql:17.2.0
container_name: palmr-postgres
ports:
- "5432:5432"
environment:
- POSTGRESQL_USERNAME=palmr
- POSTGRESQL_PASSWORD=palmr123
- POSTGRESQL_DATABASE=palmr
volumes:
- postgres_data:/bitnami/postgresql
restart: "unless-stopped"
healthcheck:
test: ["CMD", "pg_isready", "-U", "palmr"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
minio_data:

View File

@@ -1,42 +0,0 @@
services:
minio:
image: minio/minio:RELEASE.2025-03-12T18-04-18Z
container_name: minio
ports:
- "9000:9000"
- "9001:9001"
environment:
- MINIO_ROOT_USER=palmr
- MINIO_ROOT_PASSWORD=palmr123
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
restart: "unless-stopped"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/ready"]
interval: 10s
timeout: 5s
retries: 5
postgres:
image: bitnami/postgresql:17.2.0
container_name: palmr-postgres
ports:
- "5432:5432"
environment:
- POSTGRESQL_USERNAME=palmr
- POSTGRESQL_PASSWORD=palmr123
- POSTGRESQL_DATABASE=palmr
volumes:
- postgres_data:/bitnami/postgresql
restart: "unless-stopped"
healthcheck:
test: ["CMD", "pg_isready", "-U", "palmr"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
minio_data:

View File

@@ -12,9 +12,7 @@ const envSchema = z.object({
S3_REGION: z.string().optional(),
S3_BUCKET_NAME: z.string().optional(),
S3_FORCE_PATH_STYLE: z.union([z.literal("true"), z.literal("false")]).default("false"),
PORT: z.string().min(1),
DATABASE_URL: z.string().min(1),
SERVER_IP: z.string().min(1),
MAX_FILESIZE: z.string().min(1),
});

View File

@@ -70,15 +70,15 @@ async function startServer() {
app.register(healthRoutes);
await app.listen({
port: Number(env.PORT),
port: 3333,
host: "0.0.0.0",
});
console.log(`🌴 Palmr server running on port ${env.PORT} 🌴`);
console.log(`🌴 Palmr server running on port 3333 🌴`);
console.log(`📦 Storage mode: ${env.ENABLE_S3 === "true" ? "S3" : "Local Filesystem (Encrypted)"}`);
console.log("\n📚 API Documentation:");
console.log(` - API Reference: http://localhost:${env.PORT}/docs\n`);
console.log(` - API Reference: http://localhost:3333/docs\n`);
}
startServer().catch((err) => {

View File

@@ -1,46 +0,0 @@
# Use the official Node.js image as the base image
FROM node:22-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN corepack enable pnpm && pnpm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 5487
ENV PORT=5487
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View File

@@ -1,17 +0,0 @@
services:
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "6644:3000"
environment:
- NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1
- API_BASE_URL=http://host.docker.internal:3333
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -1,7 +1,14 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { IconCloudUpload, IconFileText, IconFileTypePdf, IconFileTypography, IconPhoto } from "@tabler/icons-react";
import {
IconCloudUpload,
IconFileText,
IconFileTypePdf,
IconFileTypography,
IconLoader,
IconPhoto,
} from "@tabler/icons-react";
import axios from "axios";
import { useTranslations } from "next-intl";
import { toast } from "sonner";
@@ -181,7 +188,7 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
<div className="space-y-2">
<Progress value={uploadProgress} className="w-full" />
<p className="text-sm text-gray-500 text-center">
{t("uploadFile.uploading")} {uploadProgress}% //!TODO Add translations
{t("uploadFile.uploadProgress")}: {uploadProgress}%
</p>
</div>
)}
@@ -193,7 +200,11 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP
{t("common.cancel")}
</Button>
<Button variant="default" disabled={!selectedFile || isUploading} onClick={handleUpload}>
{isUploading && <IconCloudUpload className="mr-2 h-4 w-4 animate-spin" />}
{isUploading ? (
<IconLoader className="mr-2 h-4 w-4 animate-spin" />
) : (
<IconCloudUpload className="mr-2 h-4 w-4" />
)}
{t("uploadFile.upload")}
</Button>
</DialogFooter>