chore: add Dockerfile, docker-compose, and .dockerignore for multi-service setup

Introduce a Dockerfile for building the Palmr application with multi-stage builds for both server and web components. Update docker-compose.yaml to consolidate services under a single 'palmr' service, ensuring proper health checks and environment variable configurations. Add a .dockerignore file to optimize Docker builds by excluding unnecessary files. Include a Makefile for simplified build and deployment commands.
This commit is contained in:
Daniel Luiz Alves
2025-05-27 00:50:46 -03:00
parent a9191d6b54
commit d40ef51695
16 changed files with 466 additions and 127 deletions

70
.dockerignore Normal file
View File

@@ -0,0 +1,70 @@
# Git
.git
.gitignore
# Documentation
README.md
CONTRIBUTING.md
*.md
# Node modules
node_modules
*/node_modules
**/node_modules
# Build outputs
.next
dist
build
# Development files
.env*
.vscode
.idea
# Logs
*.log
logs
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# Docker files
Dockerfile*
docker-compose*
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

160
Dockerfile Normal file
View File

@@ -0,0 +1,160 @@
FROM node:18-alpine AS base
# Install system dependencies
RUN apk add --no-cache \
netcat-openbsd \
gcompat \
supervisor \
curl
# Enable pnpm
RUN corepack enable pnpm
# Set working directory
WORKDIR /app
# === SERVER BUILD STAGE ===
FROM base AS server-deps
WORKDIR /app/server
# Copy server package files
COPY apps/server/package*.json ./
COPY apps/server/pnpm-lock.yaml ./
# Install server dependencies
RUN pnpm install --frozen-lockfile
FROM base AS server-builder
WORKDIR /app/server
# Copy server dependencies
COPY --from=server-deps /app/server/node_modules ./node_modules
# Copy server source code
COPY apps/server/ ./
# Generate Prisma client
RUN npx prisma generate
# Build server
RUN pnpm build
# === WEB BUILD STAGE ===
FROM base AS web-deps
WORKDIR /app/web
# Copy web package files
COPY apps/web/package.json apps/web/pnpm-lock.yaml ./
# Install web dependencies
RUN pnpm install --frozen-lockfile
FROM base AS web-builder
WORKDIR /app/web
# Copy web dependencies
COPY --from=web-deps /app/web/node_modules ./node_modules
# Copy web source code
COPY apps/web/ ./
# Set environment variables for build
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
# Build web application
RUN pnpm run build
# === PRODUCTION STAGE ===
FROM base AS runner
# Set production environment
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Create application user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 palmr
# Create application directories and set permissions
RUN mkdir -p /app/server /app/web /home/palmr/.npm /home/palmr/.cache
RUN chown -R palmr:nodejs /app /home/palmr
# === Copy Server Files ===
WORKDIR /app/server
# Copy server production files
COPY --from=server-builder --chown=palmr:nodejs /app/server/dist ./dist
COPY --from=server-builder --chown=palmr:nodejs /app/server/node_modules ./node_modules
COPY --from=server-builder --chown=palmr:nodejs /app/server/prisma ./prisma
COPY --from=server-builder --chown=palmr:nodejs /app/server/package.json ./
# === Copy Web Files ===
WORKDIR /app/web
# Copy web production files
COPY --from=web-builder --chown=palmr:nodejs /app/web/public ./public
COPY --from=web-builder --chown=palmr:nodejs /app/web/.next/standalone ./
COPY --from=web-builder --chown=palmr:nodejs /app/web/.next/static ./.next/static
# === Setup Supervisor ===
WORKDIR /app
# Create supervisor configuration
RUN mkdir -p /etc/supervisor/conf.d /var/log/supervisor
# Copy server start script
COPY infra/server-start.sh /app/server-start.sh
RUN chmod +x /app/server-start.sh
RUN chown palmr:nodejs /app/server-start.sh
# Copy supervisor configuration
COPY <<EOF /etc/supervisor/conf.d/supervisord.conf
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
[program:server]
command=/app/server-start.sh
directory=/app/server
user=palmr
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/server.err.log
stdout_logfile=/var/log/supervisor/server.out.log
environment=PORT=3333,HOME="/home/palmr"
[program:web]
command=node server.js
directory=/app/web
user=palmr
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/web.err.log
stdout_logfile=/var/log/supervisor/web.out.log
environment=PORT=5487,HOSTNAME="0.0.0.0",HOME="/home/palmr"
EOF
# Create main startup script
COPY <<EOF /app/start.sh
#!/bin/sh
echo "Starting Palmr Application..."
# Start supervisor
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
EOF
RUN chmod +x /app/start.sh
# Expose ports
EXPOSE 3333 5487
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:5487 || exit 1
# Start application
CMD ["/app/start.sh"]

47
Makefile Normal file
View File

@@ -0,0 +1,47 @@
.PHONY: help build start clean logs stop restart
# Default target
help:
@echo "🚀 Palmr - Available Commands:"
@echo ""
@echo " make build - Build Docker image with multi-platform support"
@echo " make start - Start the application using docker-compose"
@echo " make stop - Stop all running containers"
@echo " make logs - Show application logs"
@echo " make clean - Clean up containers and images"
@echo " make shell - Access the application container shell"
@echo ""
@echo "📁 Scripts location: ./infra/"
# Build Docker image using the build script
build:
@echo "🏗️ Building Palmr Docker image..."
@chmod +x ./infra/build-docker.sh
@./infra/build-docker.sh
# Start the application
start:
@echo "🚀 Starting Palmr application..."
@docker-compose up -d
# Stop the application
stop:
@echo "🛑 Stopping Palmr application..."
@docker-compose down
# Show logs
logs:
@echo "📋 Showing Palmr logs..."
@docker-compose logs -f
# Clean up containers and images
clean:
@echo "🧹 Cleaning up Docker containers and images..."
@docker-compose down -v
@docker system prune -f
@echo "✅ Cleanup completed!"
# Access container shell
shell:
@echo "🐚 Accessing Palmr container shell..."
@docker-compose exec palmr /bin/sh

View File

@@ -29,15 +29,16 @@ Below is the complete content of our `docker-compose.yaml` that can be copied di
```yaml ```yaml
services: services:
palmr-api: palmr:
image: kyantech/palmr-api:latest # Make sure to use the correct version (latest) of the image image: kyantech/palmr:latest # Make sure to use the correct version (latest) of the image
container_name: palmr-api container_name: palmr
depends_on: depends_on:
postgres: postgres:
condition: "service_healthy" condition: "service_healthy"
minio: minio:
condition: "service_healthy" condition: "service_healthy"
environment: environment:
# Server environment variables
- PORT=${API_INTERNAL_PORT:-3333} # Port for the backend service - PORT=${API_INTERNAL_PORT:-3333} # Port for the backend service
- DATABASE_URL=postgresql://postgres:${POSTGRESQL_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRESQL_PASSWORD env var - DATABASE_URL=postgresql://postgres:${POSTGRESQL_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRESQL_PASSWORD env var
- MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio} # This can change if your MinIO is at a different address - MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio} # This can change if your MinIO is at a different address
@@ -50,34 +51,21 @@ services:
- FRONTEND_URL=${APP_URL:-http://${SERVER_IP:-localhost}:${APP_EXTERNAL_PORT:-5487}} # Frontend URL - Make sure to use the correct frontend URL, depends on where the frontend is running, its prepared for localhost, but you can change it to your frontend URL if needed - FRONTEND_URL=${APP_URL:-http://${SERVER_IP:-localhost}:${APP_EXTERNAL_PORT:-5487}} # Frontend URL - Make sure to use the correct frontend URL, depends on where the frontend is running, its prepared for localhost, but you can change it to your frontend URL if needed
- SERVER_IP=${SERVER_IP:-localhost} # Server IP - Make sure to use the correct server IP if you running on a cloud provider or a virtual machine. This prepared for localhost, but you can change it to your server IP if needed - SERVER_IP=${SERVER_IP:-localhost} # Server IP - Make sure to use the correct server IP if you running on a cloud provider or a virtual machine. This prepared for localhost, but you can change it to your server IP if needed
- MAX_FILESIZE=${MAX_FILESIZE:-1073741824} # Max Filesize for upload - Declared in Bytes. Default is 1GiB - MAX_FILESIZE=${MAX_FILESIZE:-1073741824} # Max Filesize for upload - Declared in Bytes. Default is 1GiB
ports:
- "${API_EXTERNAL_PORT:-3333}:${API_INTERNAL_PORT:-3333}" # Backend port mapping # Web environment variables
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "http://palmr-api:${API_INTERNAL_PORT:-3333}/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
palmr-app:
image: kyantech/palmr-app:latest # Make sure to use the correct version (latest) of the image
container_name: palmr-web
depends_on:
palmr-api:
condition: "service_healthy"
ports:
- "${APP_EXTERNAL_PORT:-5487}:5487" # Frontend port mapping
environment:
- NODE_ENV=production - NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1 - NEXT_TELEMETRY_DISABLED=1
- API_BASE_URL=http://palmr-api:${API_INTERNAL_PORT:-3333} # Here we use docker's internal network to reference the backend service (can be changed if needed) - API_BASE_URL=http://palmr:${API_INTERNAL_PORT:-3333} # Using Docker service name for internal communication
ports:
- "${API_EXTERNAL_PORT:-3333}:3333" # Server port
- "${APP_EXTERNAL_PORT:-5487}:5487" # Web port
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5487"] test: ["CMD", "curl", "-f", "http://localhost:3333/health", "&&", "curl", "-f", "http://localhost:5487"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
start_period: 60s
minio: minio:
image: minio/minio:RELEASE.2025-03-12T18-04-18Z # Use only version RELEASE.2025-03-12T18-04-18Z to avoid compatibility issues with the backend image: minio/minio:RELEASE.2025-03-12T18-04-18Z # Use only version RELEASE.2025-03-12T18-04-18Z to avoid compatibility issues with the backend
@@ -138,7 +126,7 @@ services:
volumes: volumes:
minio_data: minio_data:
postgres_data: postgres_data:
``` ```
@@ -148,12 +136,11 @@ Notice that the `docker-compose.yaml` has several comments that help you configu
### ⚙️ Services Overview ### ⚙️ Services Overview
Palmr. consists of five main services, each with specific responsibilities. Below, we present a detailed view of each component: Palmr. consists of four main services, each with specific responsibilities. Below, we present a detailed view of each component:
| **Service** | **Image** | **Exposed Ports** | **Main Features** | | **Service** | **Image** | **Exposed Ports** | **Main Features** |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| palmr-api (Backend) | [kyantech/palmr-api:latest](https://hub.docker.com/repository/docker/kyantech/palmr-api/tags/latest/sha256-84245d3d0012aa65c588caba56322363729c4b68c3722a08dcda912904de9d1d) | **3333** | • Main API service<br/>• Depends on services: postgres and minio<br/>• Has healthcheck to ensure availability | | palmr | [kyantech/palmr:latest](https://hub.docker.com/repository/docker/kyantech/palmr/general) | **3333** (API)<br/>**5487** (Web) | • Combined backend API and frontend service<br/>• Depends on services: postgres and minio<br/>• Has healthcheck to ensure availability |
| palmr-app (Frontend) | [kyantech/palmr-app:latest](https://hub.docker.com/repository/docker/kyantech/palmr-app/tags/latest/sha256-33f568673ae8cc8529532146b6afc1acafa203387ced6c7bb3451a7ab4198a2f) | **5487** | • Web application interface<br/>• Depends on palmr-api service<br/>• Configured for production environment |
| minio (Storage) | [minio/minio:RELEASE.2025-03-12T18-04-18Z](https://hub.docker.com/layers/minio/minio/RELEASE.2025-03-12T18-04-18Z/images/sha256-85f3e4cd1ca92a2711553ab79f222bcd8b75aa2c77a1a0b0ccf80d38e8ab2fe5) | **6421**(API)<br/>**6422**(Console) | • File storage service<br/>• Persistent volume for data | | minio (Storage) | [minio/minio:RELEASE.2025-03-12T18-04-18Z](https://hub.docker.com/layers/minio/minio/RELEASE.2025-03-12T18-04-18Z/images/sha256-85f3e4cd1ca92a2711553ab79f222bcd8b75aa2c77a1a0b0ccf80d38e8ab2fe5) | **6421**(API)<br/>**6422**(Console) | • File storage service<br/>• Persistent volume for data |
| minio-init (Config) | [minio/mc:RELEASE.2025-03-12T17-29-24Z](https://hub.docker.com/layers/minio/mc/RELEASE.2025-03-12T17-29-24Z/images/sha256-68d8c80f43908b02daa285e55547131870a1d36b3ffe272c26d7d8f4d52d1e5c) | N/A | • Initially configures the "files" bucket<br/>• Runs only once during initialization | | minio-init (Config) | [minio/mc:RELEASE.2025-03-12T17-29-24Z](https://hub.docker.com/layers/minio/mc/RELEASE.2025-03-12T17-29-24Z/images/sha256-68d8c80f43908b02daa285e55547131870a1d36b3ffe272c26d7d8f4d52d1e5c) | N/A | • Initially configures the "files" bucket<br/>• Runs only once during initialization |
| postgres (Database) | [bitnami/postgresql:17.2.0](https://hub.docker.com/layers/bitnami/postgresql/17.2.0/images/sha256-29c614afad4f514b12b5c0f4d006f38c98fa4b18c3582732ff93b3fe9a79d875) | **5432** | • PostgreSQL database<br/>• Persistent volume for data | | postgres (Database) | [bitnami/postgresql:17.2.0](https://hub.docker.com/layers/bitnami/postgresql/17.2.0/images/sha256-29c614afad4f514b12b5c0f4d006f38c98fa4b18c3582732ff93b3fe9a79d875) | **5432** | • PostgreSQL database<br/>• Persistent volume for data |
@@ -197,13 +184,17 @@ The table below shows all environment variables that can be set
In a localhost environment, there's no mystery. If you don't want to change any service exposure ports, you can simply run: In a localhost environment, there's no mystery. If you don't want to change any service exposure ports, you can simply run:
```bash
docker compose pull && docker compose up -d
```
This will execute all necessary services and give you access to the following URLs (if you haven't changed any ports): This will execute all necessary services and give you access to the following URLs (if you haven't changed any ports):
- **Frontend:** [http://localhost:5487](http://localhost:5487) - **Frontend:** [http://localhost:5487](http://localhost:5487)
- **Backend:** [http://localhost:3333](http://localhost:3333/) - **Backend:** [http://localhost:3333](http://localhost:3333/)
- **MinIO API:** [http://localhost:6421](http://localhost:6421) - **MinIO API:** [http://localhost:6421](http://localhost:6421)
- **MinIO Console:** [http://localhost:6422](http://localhost:6422) - **MinIO Console:** [http://localhost:6422](http://localhost:6422)
- **Postgres Database:** [http://localhost:5423](http://localhost:5423/) (Connection only) - **Postgres Database:** [http://localhost:5432](http://localhost:5432/) (Connection only)
> *If you have changed any port, simply access the URL with the port you configured.* > *If you have changed any port, simply access the URL with the port you configured.*
> >
@@ -247,7 +238,7 @@ If you haven't changed the execution ports, you'll have access on your server at
- **Backend:** `[server_ip]:3333` - **Backend:** `[server_ip]:3333`
- **MinIO API:** `[server_ip]:6421` - **MinIO API:** `[server_ip]:6421`
- **MinIO Console:** `[server_ip]:6422` - **MinIO Console:** `[server_ip]:6422`
- **Postgres Database:** `[server_ip]:5423` (Connection only) - **Postgres Database:** `[server_ip]:5432` (Connection only)
> *If you've changed any port, simply access the URL with the port you configured.* > *If you've changed any port, simply access the URL with the port you configured.*
> >

View File

@@ -25,4 +25,4 @@
"gh-sponsor", "gh-sponsor",
"..." "..."
] ]
} }

View File

@@ -83,7 +83,12 @@ export default function HomePage() {
function Hero() { function Hero() {
return ( return (
<section className="relative z-[2] flex flex-col border-x border-t px-6 pt-12 pb-10 md:px-12 md:pt-16 max-md:text-center"> <section className="relative z-[2] flex flex-col border-x border-t px-6 pt-12 pb-10 md:px-12 md:pt-16 max-md:text-center">
<h1 className="mb-8 text-5xl font-bold">🌴 Palmr. <span className="text-[10px] font-light text-muted-foreground/50 font-mono">v2.0.0-beta</span></h1> <h1 className="mb-8 text-5xl font-bold">
🌴 Palmr.{" "}
<span className="text-[10px] font-light text-muted-foreground/50 font-mono">
v2.0.0-beta
</span>
</h1>
<h1 className="hidden text-4xl font-medium max-w-[600px] md:block mb-4"> <h1 className="hidden text-4xl font-medium max-w-[600px] md:block mb-4">
Modern & efficient file sharing Modern & efficient file sharing
</h1> </h1>

View File

@@ -1,6 +1,6 @@
import { source } from '@/lib/source'; import { source } from "@/lib/source";
import { createFromSource } from 'fumadocs-core/search/server'; import { createFromSource } from "fumadocs-core/search/server";
export const { GET } = createFromSource(source, (page) => { export const { GET } = createFromSource(source, (page) => {
return { return {
title: page.data.title, title: page.data.title,
@@ -8,6 +8,8 @@ export const { GET } = createFromSource(source, (page) => {
url: page.url, url: page.url,
id: page.url, id: page.url,
structuredData: page.data.structuredData, structuredData: page.data.structuredData,
tag: page.url.startsWith('/docs/2.0.0-beta') ? 'v2.0.0-beta' : 'v1.1.7-beta' tag: page.url.startsWith("/docs/2.0.0-beta")
? "v2.0.0-beta"
: "v1.1.7-beta",
}; };
}); });

View File

@@ -11,14 +11,15 @@ const inter = Inter({
export const metadata = { export const metadata = {
title: "🌴 Palmr. | Official Website", title: "🌴 Palmr. | Official Website",
description: "Palmr. is a fast, simple and powerful document sharing platform.", description:
"Palmr. is a fast, simple and powerful document sharing platform.",
}; };
export default function Layout({ children }: { children: ReactNode }) { export default function Layout({ children }: { children: ReactNode }) {
return ( return (
<html lang="en" className={inter.className} suppressHydrationWarning> <html lang="en" className={inter.className} suppressHydrationWarning>
<body className="flex flex-col min-h-screen"> <body className="flex flex-col min-h-screen">
<Banner variant="rainbow" id="banner-v-2"> <Banner variant="rainbow" id="banner-v-3">
<Link href="/docs/2.0.0-beta">Palmr. v2.0.0-beta has released!</Link> <Link href="/docs/2.0.0-beta">Palmr. v2.0.0-beta has released!</Link>
</Banner> </Banner>
<RootProvider <RootProvider

View File

@@ -11,7 +11,10 @@
"lint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --fix", "lint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
"format": "prettier . --write", "format": "prettier . --write",
"format:check": "prettier . --check", "format:check": "prettier . --check",
"db:seed": "ts-node prisma/seed.ts" "db:seed": "ts-node prisma/seed.js"
},
"prisma": {
"seed": "node prisma/seed.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",

View File

@@ -1,6 +1,8 @@
import { prisma } from "../src/shared/prisma"; /* eslint-disable no-undef */
import crypto from "node:crypto"; const { PrismaClient } = require('@prisma/client');
import { env } from '../src/env'; const crypto = require('crypto');
const prisma = new PrismaClient();
const defaultConfigs = [ const defaultConfigs = [
// General Configurations // General Configurations
@@ -37,7 +39,7 @@ const defaultConfigs = [
// Storage Configurations // Storage Configurations
{ {
key: "maxFileSize", key: "maxFileSize",
value: env.MAX_FILESIZE, // default 1GiB in bytes - 1073741824 value: process.env.MAX_FILESIZE || "1073741824", // default 1GiB in bytes
type: "bigint", type: "bigint",
group: "storage", group: "storage",
}, },
@@ -124,28 +126,37 @@ const defaultConfigs = [
]; ];
async function main() { async function main() {
console.log("Seeding app configurations..."); console.log("🌱 Starting app configurations seed...");
console.log("🛡️ Protected mode: Only creates missing configurations");
let createdCount = 0;
let skippedCount = 0;
for (const config of defaultConfigs) { for (const config of defaultConfigs) {
if (config.key === "jwtSecret") { // Check if configuration already exists
const existingSecret = await prisma.appConfig.findUnique({ const existingConfig = await prisma.appConfig.findUnique({
where: { key: "jwtSecret" }, where: { key: config.key },
}); });
if (existingSecret) { if (existingConfig) {
console.log("JWT secret already exists, skipping..."); console.log(`⏭️ Configuration '${config.key}' already exists, skipping...`);
continue; skippedCount++;
} continue;
} }
await prisma.appConfig.upsert({ // Only create if it doesn't exist
where: { key: config.key }, await prisma.appConfig.create({
update: config, data: config,
create: config,
}); });
console.log(`✅ Created configuration: ${config.key}`);
createdCount++;
} }
console.log("App configurations seeded successfully!"); console.log("\n📊 Seed Summary:");
console.log(` ✅ Created: ${createdCount} configurations`);
console.log(` ⏭️ Skipped: ${skippedCount} configurations`);
console.log("🎉 App configurations seeded successfully!");
} }
main() main()
@@ -155,4 +166,4 @@ main()
}) })
.finally(async () => { .finally(async () => {
await prisma.$disconnect(); await prisma.$disconnect();
}); });

View File

@@ -1,17 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const count = await prisma.user.count();
console.log(count);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@@ -1,27 +0,0 @@
#!/bin/sh
echo "Waiting for PostgreSQL..."
while ! nc -z postgres 5432; do
sleep 1
done
echo "PostgreSQL is up!"
echo "Generating Prisma client..."
npx prisma generate --schema=./prisma/schema.prisma
# Check if database needs migrations
echo "Checking migrations..."
npx prisma migrate deploy
# Check if database is empty using Prisma
USER_COUNT=$(node ./scripts/check-db.mjs)
if [ "$USER_COUNT" -eq "0" ]; then
echo "Database is empty, running seeds..."
pnpm db:seed
else
echo "Database already has data, skipping seeds..."
fi
echo "Starting application..."
pnpm start

View File

@@ -1,13 +1,14 @@
services: services:
palmr-api: palmr:
image: kyantech/palmr-api:latest # Make sure to use the correct version (latest) of the image image: kyantech/palmr:latest # Make sure to use the correct version (latest) of the image
container_name: palmr-api container_name: palmr
depends_on: depends_on:
postgres: postgres:
condition: "service_healthy" condition: "service_healthy"
minio: minio:
condition: "service_healthy" condition: "service_healthy"
environment: environment:
# Server environment variables
- PORT=${API_INTERNAL_PORT:-3333} # Port for the backend service - PORT=${API_INTERNAL_PORT:-3333} # Port for the backend service
- DATABASE_URL=postgresql://postgres:${POSTGRESQL_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRESQL_PASSWORD env var - DATABASE_URL=postgresql://postgres:${POSTGRESQL_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRESQL_PASSWORD env var
- MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio} # This can change if your MinIO is at a different address - MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio} # This can change if your MinIO is at a different address
@@ -20,34 +21,21 @@ services:
- FRONTEND_URL=${APP_URL:-http://${SERVER_IP:-localhost}:${APP_EXTERNAL_PORT:-5487}} # Frontend URL - Make sure to use the correct frontend URL, depends on where the frontend is running, its prepared for localhost, but you can change it to your frontend URL if needed - FRONTEND_URL=${APP_URL:-http://${SERVER_IP:-localhost}:${APP_EXTERNAL_PORT:-5487}} # Frontend URL - Make sure to use the correct frontend URL, depends on where the frontend is running, its prepared for localhost, but you can change it to your frontend URL if needed
- SERVER_IP=${SERVER_IP:-localhost} # Server IP - Make sure to use the correct server IP if you running on a cloud provider or a virtual machine. This prepared for localhost, but you can change it to your server IP if needed - SERVER_IP=${SERVER_IP:-localhost} # Server IP - Make sure to use the correct server IP if you running on a cloud provider or a virtual machine. This prepared for localhost, but you can change it to your server IP if needed
- MAX_FILESIZE=${MAX_FILESIZE:-1073741824} # Max Filesize for upload - Declared in Bytes. Default is 1GiB - MAX_FILESIZE=${MAX_FILESIZE:-1073741824} # Max Filesize for upload - Declared in Bytes. Default is 1GiB
ports:
- "${API_EXTERNAL_PORT:-3333}:${API_INTERNAL_PORT:-3333}" # Backend port mapping # Web environment variables
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "http://palmr-api:${API_INTERNAL_PORT:-3333}/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
palmr-app:
image: kyantech/palmr-app:latest # Make sure to use the correct version (latest) of the image
container_name: palmr-web
depends_on:
palmr-api:
condition: "service_healthy"
ports:
- "${APP_EXTERNAL_PORT:-5487}:5487" # Frontend port mapping
environment:
- NODE_ENV=production - NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1 - NEXT_TELEMETRY_DISABLED=1
- API_BASE_URL=http://palmr-api:${API_INTERNAL_PORT:-3333} # Here we use docker's internal network to reference the backend service (can be changed if needed) - API_BASE_URL=http://palmr:${API_INTERNAL_PORT:-3333} # Using Docker service name for internal communication
ports:
- "${API_EXTERNAL_PORT:-3333}:3333" # Server port
- "${APP_EXTERNAL_PORT:-5487}:5487" # Web port
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5487"] test: ["CMD", "wget", "--no-verbose", "http://palmr:${API_INTERNAL_PORT:-3333}/health", "&&", "wget", "--no-verbose", "http://palmr:${APP_INTERNAL_PORT:-5487}"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
start_period: 60s
minio: minio:
image: minio/minio:RELEASE.2025-03-12T18-04-18Z # Use only version RELEASE.2025-03-12T18-04-18Z to avoid compatibility issues with the backend image: minio/minio:RELEASE.2025-03-12T18-04-18Z # Use only version RELEASE.2025-03-12T18-04-18Z to avoid compatibility issues with the backend
@@ -108,4 +96,4 @@ services:
volumes: volumes:
minio_data: minio_data:
postgres_data: postgres_data:

33
infra/SCRIPTS.md Normal file
View File

@@ -0,0 +1,33 @@
## 🚀 Quick Start
Palmr. includes a convenient Makefile to simplify development and deployment tasks:
```bash
# Show all available commands
make help
# Build Docker image with multi-platform support
make build
# Start the application
make start
# View application logs
make logs
# Stop the application
make stop
# Clean up containers and images
make clean
```
### Available Commands:
- `make build` - Build Docker image using the build script in `./infra/`
- `make start` - Start the application using docker-compose
- `make stop` - Stop all running containers
- `make logs` - Show application logs
- `make clean` - Clean up containers and images
- `make shell` - Access the application container shell
All infrastructure scripts are organized in the `./infra/` directory for better project organization.

43
infra/build-docker.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Ask for tag interactively
echo "🏷️ Please enter a tag for the build (e.g., v1.0.0, production, beta):"
read -p "Tag: " TAG
# Check if tag was provided
if [ -z "$TAG" ]; then
echo "❌ Error: Tag cannot be empty"
echo "Please run the script again and provide a valid tag"
exit 1
fi
echo "🚀 Building Palmr Unified Image for AMD64 and ARM..."
echo "📦 Building tags: latest and $TAG"
# Ensure buildx is available and create/use a builder instance
docker buildx create --name palmr-builder --use 2>/dev/null || docker buildx use palmr-builder
# Build the unified image for multiple platforms without cache
docker buildx build \
--platform linux/amd64,linux/arm64 \
--no-cache \
-t kyantech/palmr:latest \
-t kyantech/palmr:$TAG \
--load \
.
if [ $? -eq 0 ]; then
echo "✅ Multi-platform build completed successfully!"
echo ""
echo "Built for platforms: linux/amd64, linux/arm64"
echo "Built tags: palmr:latest and palmr:$TAG"
echo ""
echo "Access points:"
echo "- API: http://localhost:3333"
echo "- Web App: http://localhost:5487"
echo ""
echo "Read the docs for more information"
else
echo "❌ Build failed!"
exit 1
fi

29
infra/server-start.sh Normal file
View File

@@ -0,0 +1,29 @@
#!/bin/sh
echo "Starting Palmr Server..."
# Set proper environment
export HOME=/home/palmr
export NPM_CONFIG_CACHE=/home/palmr/.npm
export PNPM_HOME=/home/palmr/.pnpm
# Wait for PostgreSQL
echo "Waiting for PostgreSQL..."
while ! nc -z postgres 5432; do
sleep 1
done
echo "PostgreSQL is up!"
cd /app/server
echo "Generating Prisma client..."
npx prisma generate --schema=./prisma/schema.prisma
echo "Running migrations..."
npx prisma migrate deploy
echo "Running database seeds..."
node prisma/seed.js || echo "Seeds failed or already exist, continuing..."
echo "Starting server application..."
exec node dist/server.js