commit aa90835d84f9b0c895df7bbc0e096801e44cc877 Author: Amirhossein Khalili Date: Tue Mar 17 19:33:46 2026 +0800 initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4bec3ae --- /dev/null +++ b/.env.example @@ -0,0 +1,55 @@ +# Penpot Environment Variables Template +# Copy this file to .env and fill in the secure values. + +# ========================================== +# ENVIRONMENT TOGGLE: DEV vs PRODUCTION +# ========================================== + +# ---> OPTION A: DEVELOPMENT (No SSL / Plain HTTP) +# Prefix your IP or localhost with http:// to disable SSL generation. +# CADDY_SITE_ADDRESS=http://192.168.1.50 +# PENPOT_PUBLIC_URI=http://192.168.1.50 + +# ---> OPTION B: PRODUCTION (Automatic SSL via Let's Encrypt) +# Comment out Option A, and uncomment these two lines. +# Do NOT prefix CADDY_SITE_ADDRESS with https://, just the domain. +# CADDY_SITE_ADDRESS=design.yourdomain.com +# PENPOT_PUBLIC_URI=https://design.yourdomain.com + +# ========================================== + +# --- Domain and SSL Setup --- +CADDY_SITE_ADDRESS=https://design.yourdomain.com +PENPOT_PUBLIC_URI=https://design.yourdomain.com +TLS_EMAIL=admin@yourdomain.com + +# --- Security --- +# Generate a random string for this (e.g., using `openssl rand -base64 32`) +PENPOT_SECRET_KEY=your_super_secret_key_here + +# --- Database Setup --- +# Must match between PostgreSQL and Backend +POSTGRES_USER=penpot +POSTGRES_PASSWORD=your_secure_db_password +POSTGRES_DB=penpot +PENPOT_DATABASE_URI=postgresql://penpot-postgres/penpot +PENPOT_DATABASE_USERNAME=penpot +PENPOT_DATABASE_PASSWORD=your_secure_db_password + +# --- Redis Setup --- +PENPOT_REDIS_URI=redis://penpot-redis/0 + +# --- Telemetry (Optional) --- +# Set to true to disable sending anonymous telemetry to Penpot +PENPOT_TELEMETRY_ENABLED=false + +# --- Email (SMTP) Configuration --- +# Required for user registration, password resets, and team invites +PENPOT_SMTP_DEFAULT_FROM=penpot@yourdomain.com +PENPOT_SMTP_DEFAULT_REPLY_TO=penpot@yourdomain.com +PENPOT_SMTP_HOST=smtp.yourprovider.com +PENPOT_SMTP_PORT=587 +PENPOT_SMTP_USERNAME=your_smtp_username +PENPOT_SMTP_PASSWORD=your_smtp_password +PENPOT_SMTP_TLS=true +PENPOT_SMTP_SSL=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4f5dec --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Ignore environment variables containing secrets +.env + +# Ignore persistent data volumes +volumes/ +*/volumes/ +*data/ +assets/ + +# Ignore backups +backups/ +*.tar.gz +*.sql + +# OS generated files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..01babe5 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Self-Hosted Penpot Deployment + +This repository contains the infrastructure configuration to run Penpot via Docker Compose. + +## Deployment Instructions + +1. **Clone the repository:** + git clone http://git.amiirkhl.ir/interanet/penpot-deployment.git + cd penpot-deployment + +2. **Setup Environment Variables:** + cp .env.example .env + # Edit the .env file and add your secret keys, passwords, and SMTP details + nano .env + +3. **Start the Services:** + docker compose up -d + +4. **Create the First Admin User:** + Once the containers are running, you need to create your main admin account via the command line: + docker exec -it penpot-backend ./manage.sh create-profile diff --git a/config/caddy/Caddyfile b/config/caddy/Caddyfile new file mode 100644 index 0000000..6c7ce44 --- /dev/null +++ b/config/caddy/Caddyfile @@ -0,0 +1,4 @@ +{$DOMAIN} { + tls {$TLS_EMAIL} + reverse_proxy penpot-frontend:80 +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..801be31 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,77 @@ +version: "3.8" + +networks: + penpot: + +volumes: + penpot_postgres_v15: + penpot_tenant_assets: + caddy_data: + caddy_config: + +services: + caddy: + image: caddy:2 + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - ./config/caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + environment: + - CADDY_SITE_ADDRESS=${CADDY_SITE_ADDRESS} + - TLS_EMAIL=${TLS_EMAIL} + networks: + - penpot + depends_on: + - penpot-frontend + + penpot-frontend: + image: "penpotapp/frontend:latest" + restart: always + volumes: + - penpot_tenant_assets:/opt/data/assets + depends_on: + - penpot-backend + networks: + - penpot + + penpot-backend: + image: "penpotapp/backend:latest" + restart: always + volumes: + - penpot_tenant_assets:/opt/data/assets + depends_on: + - penpot-postgres + - penpot-redis + networks: + - penpot + env_file: + - .env + + penpot-exporter: + image: "penpotapp/exporter:latest" + restart: always + networks: + - penpot + env_file: + - .env + + penpot-postgres: + image: "postgres:15" + restart: always + stop_signal: SIGINT + volumes: + - penpot_postgres_v15:/var/lib/postgresql/data + networks: + - penpot + env_file: + - .env + + penpot-redis: + image: redis:7 + restart: always + networks: + - penpot diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..ad3cca4 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +BACKUP_DIR="./backups" +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") +DB_BACKUP_FILE="${BACKUP_DIR}/penpot_db_${TIMESTAMP}.sql" +ASSETS_BACKUP_FILE="${BACKUP_DIR}/penpot_assets_${TIMESTAMP}.tar.gz" + +echo "=== Starting Penpot Backup ($TIMESTAMP) ===" + +mkdir -p "$BACKUP_DIR" + +# Backup PostgreSQL Database +echo "Backing up Database..." +docker compose exec -T penpot-postgres pg_dump -U penpot -d penpot -c > "$DB_BACKUP_FILE" +echo "Database backed up to $DB_BACKUP_FILE" + +# Backup Assets Volume +echo "Backing up Assets..." +docker compose run --rm \ + -v $(pwd)/backups:/backups \ + penpot-backend \ + tar czvf /backups/penpot_assets_${TIMESTAMP}.tar.gz -C /opt/data/assets . + +echo "Assets backed up to $ASSETS_BACKUP_FILE" + +echo "=== Backup Complete! ===" diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100644 index 0000000..9fd0ace --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + echo "Example: $0 backups/penpot_db_20231026_120000.sql backups/penpot_assets_20231026_120000.tar.gz" + exit 1 +fi + +DB_FILE=$1 +ASSETS_FILE=$2 + +# Verify files exist +if [ ! -f "$DB_FILE" ]; then + echo "Error: Database backup file '$DB_FILE' not found." + exit 1 +fi + +if [ ! -f "$ASSETS_FILE" ]; then + echo "Error: Assets backup file '$ASSETS_FILE' not found." + exit 1 +fi + +echo "=== Starting Penpot Restore ===" + +echo "Stopping frontend, backend, and exporter..." +docker compose stop penpot-frontend penpot-backend penpot-exporter + +echo "Restoring Database from $DB_FILE..." +cat "$DB_FILE" | docker compose exec -T penpot-postgres psql -U penpot -d penpot +echo "Database restored." + +# We spin up a temporary container, delete current assets, and extract the backup +# Convert relative path of ASSETS_FILE to absolute path for the volume mount +echo "Restoring Assets from $ASSETS_FILE..." +ABS_ASSETS_FILE=$(realpath "$ASSETS_FILE") +ABS_ASSETS_DIR=$(dirname "$ABS_ASSETS_FILE") +ASSETS_FILENAME=$(basename "$ABS_ASSETS_FILE") + +docker compose run --rm \ + -v "$ABS_ASSETS_DIR":/backups \ + penpot-backend \ + sh -c "rm -rf /opt/data/assets/* && tar xzvf /backups/$ASSETS_FILENAME -C /opt/data/assets/" +echo "Assets restored." + +echo "Restarting all Penpot services..." +docker compose start + +echo "=== Restore Complete! ===" diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100644 index 0000000..460951b --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +echo "Pulling latest Penpot images..." +docker compose pull + +echo "Recreating containers..." +docker compose up -d + +echo "Removing old unused images..." +docker image prune -f + +echo "Penpot update complete!"