From 7d1acd8469fb2a6c508d42b22b7521e18f6c1cbf Mon Sep 17 00:00:00 2001 From: berkay Date: Sat, 19 Apr 2025 20:00:57 +0300 Subject: [PATCH] postgres service for external use --- .env.example | 13 +++ Makefile | 75 ++++++++++++++ README.md | 178 ++++++++++++++++++++++++++++++++ config/postgres-dev.conf | 62 +++++++++++ config/postgres-production.conf | 75 ++++++++++++++ config/postgres-staging.conf | 64 ++++++++++++ config/postgres.conf | 65 ++++++++++++ docker-compose.override.yaml | 25 +++++ docker-compose.yaml | 51 +++++++++ environments/dev.yaml | 33 ++++++ environments/production.yaml | 82 +++++++++++++++ environments/staging.yaml | 43 ++++++++ init/01-init.sql | 84 +++++++++++++++ scripts/backup.sh | 60 +++++++++++ scripts/monitor.sh | 68 ++++++++++++ scripts/restore.sh | 94 +++++++++++++++++ scripts/setup.sh | 21 ++++ 17 files changed, 1093 insertions(+) create mode 100644 .env.example create mode 100644 Makefile create mode 100644 README.md create mode 100644 config/postgres-dev.conf create mode 100644 config/postgres-production.conf create mode 100644 config/postgres-staging.conf create mode 100644 config/postgres.conf create mode 100644 docker-compose.override.yaml create mode 100644 docker-compose.yaml create mode 100644 environments/dev.yaml create mode 100644 environments/production.yaml create mode 100644 environments/staging.yaml create mode 100644 init/01-init.sql create mode 100644 scripts/backup.sh create mode 100644 scripts/monitor.sh create mode 100644 scripts/restore.sh create mode 100644 scripts/setup.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..57262c5 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# PostgreSQL Configuration +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres_secure_password +POSTGRES_DB=postgres + +# Backup Configuration +BACKUP_DIR=./backups +BACKUP_RETENTION_DAYS=7 + +# Resource Limits (for container configuration) +# Recommended: 2 CPU cores, 4GB RAM minimum +POSTGRES_CPU_LIMIT=2 +POSTGRES_MEMORY_LIMIT=4G diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3271003 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +# Makefile for PostgreSQL Service + +.PHONY: setup start stop restart status logs backup restore monitor clean help + +# Default environment +ENV ?= dev + +help: + @echo "PostgreSQL Service Management" + @echo "============================" + @echo "Available commands:" + @echo " make setup - Setup the PostgreSQL service (make scripts executable)" + @echo " make start - Start the PostgreSQL service" + @echo " make stop - Stop the PostgreSQL service" + @echo " make restart - Restart the PostgreSQL service" + @echo " make status - Check the status of the PostgreSQL service" + @echo " make logs - View the logs of the PostgreSQL service" + @echo " make backup - Create a backup of the PostgreSQL database" + @echo " make restore - Restore a backup of the PostgreSQL database" + @echo " make monitor - Monitor the PostgreSQL service" + @echo " make clean - Remove all containers, volumes, and networks" + @echo "" + @echo "Environment options:" + @echo " make start ENV=dev - Start with development configuration (default)" + @echo " make start ENV=staging - Start with staging configuration" + @echo " make start ENV=production - Start with production configuration" + +setup: + @echo "Setting up PostgreSQL service..." + @cd scripts && ./setup.sh + +start: + @echo "Starting PostgreSQL service with $(ENV) environment..." +ifeq ($(ENV), dev) + @docker-compose up -d +else + @docker-compose -f docker-compose.yaml -f environments/$(ENV).yaml up -d +endif + @echo "PostgreSQL service started." + +stop: + @echo "Stopping PostgreSQL service..." +ifeq ($(ENV), dev) + @docker-compose down +else + @docker-compose -f docker-compose.yaml -f environments/$(ENV).yaml down +endif + @echo "PostgreSQL service stopped." + +restart: stop start + +status: + @echo "PostgreSQL service status:" + @docker-compose ps + +logs: + @echo "PostgreSQL service logs:" + @docker-compose logs -f postgres + +backup: + @echo "Creating PostgreSQL backup..." + @cd scripts && ./backup.sh + +restore: + @echo "Restoring PostgreSQL backup..." + @cd scripts && ./restore.sh $(BACKUP) + +monitor: + @echo "Monitoring PostgreSQL service..." + @cd scripts && ./monitor.sh + +clean: + @echo "Cleaning up PostgreSQL service..." + @docker-compose down -v --remove-orphans + @echo "PostgreSQL service cleaned up." diff --git a/README.md b/README.md new file mode 100644 index 0000000..085c57a --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# PostgreSQL Production Setup for Proxmox LXC Container + +This repository contains a production-ready PostgreSQL setup using Docker Compose, designed to run on a Proxmox LXC container. + +## Overview + +The configuration includes: + +- PostgreSQL 15 with optimized configuration +- Persistent data storage +- Security features +- Health checks +- Resource limits +- Backup and restore capabilities + +## Prerequisites + +- Proxmox VE with LXC container support +- Docker and Docker Compose installed on the LXC container +- Proper network configuration in Proxmox + +## Configuration Details + +### docker-compose.yml Explained + +```yaml +services: + postgres: + image: postgres:15 # Using PostgreSQL 15 + container_name: postgres + restart: always # Ensures PostgreSQL restarts automatically + environment: + # Environment variables for authentication + - POSTGRES_USER=${POSTGRES_USER:-postgres} # Default: postgres + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} # Default: password + - POSTGRES_DB=${POSTGRES_DB:-postgres} # Default: postgres + - PGDATA=/var/lib/postgresql/data/pgdata + volumes: + # Persistent data storage + - postgres_data:/var/lib/postgresql/data # Database files + - ./config/postgres.conf:/etc/postgresql/postgresql.conf # Configuration file + - ./init:/docker-entrypoint-initdb.d # Initialization scripts + ports: + - "5432:5432" # Expose PostgreSQL port + command: postgres -c config_file=/etc/postgresql/postgresql.conf + healthcheck: + # Regular health checks + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres}", + ] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - postgres_network + ulimits: + # Increase file descriptor limits for production + nofile: + soft: 64000 + hard: 64000 + logging: + # Log rotation to prevent disk space issues + driver: "json-file" + options: + max-size: "200m" + max-file: "10" + +volumes: + postgres_data: # Persistent volume for database files + driver: local + +networks: + postgres_network: + driver: bridge +``` + +## Security Considerations + +1. **Authentication**: PostgreSQL is configured with authentication enabled by default +2. **Environment Variables**: Sensitive information is passed via environment variables +3. **Network Isolation**: Services run on a dedicated bridge network +4. **Configuration**: Optimized PostgreSQL configuration for security and performance + +## Initialization Script + +The initialization scripts in the `init/` directory: + +- Create default roles and permissions +- Set up sample schemas and tables +- Configure database parameters for optimal performance + +## Usage + +1. Create a `.env` file with your custom credentials: + +``` +POSTGRES_USER=your_postgres_username +POSTGRES_PASSWORD=your_secure_password +POSTGRES_DB=your_database_name +``` + +2. Start the services: + +```bash +docker-compose up -d +``` + +3. Connect to PostgreSQL: + +``` +psql -h your-server-ip -p 5432 -U your_postgres_username -d your_database_name +``` + +## Backup and Restore + +### Creating a Backup + +```bash +./scripts/backup.sh +``` + +### Restoring from Backup + +```bash +./scripts/restore.sh +``` + +## Proxmox LXC Container Configuration + +For optimal performance in a Proxmox LXC container: + +1. Ensure the container has sufficient resources: + + - At least 2 CPU cores + - Minimum 4GB RAM + - At least 20GB storage + +2. Enable necessary features in the LXC container: + + ``` + pct set -features nesting=1 + ``` + +3. Configure container for Docker: + ``` + echo 'kernel.unprivileged_userns_clone=1' > /etc/sysctl.d/unprivileged-userns-clone.conf + sysctl -p /etc/sysctl.d/unprivileged-userns-clone.conf + ``` + +## Maintenance + +- **Backups**: PostgreSQL data is stored in named volumes. Use Docker's volume backup mechanisms: + + ```bash + docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar -czf /backup/postgres-data-backup.tar.gz /data + ``` + +- **Monitoring**: The service is configured with health checks and can be integrated with monitoring tools like Prometheus and Grafana + +- **Updating**: To update PostgreSQL version, change the image tag in docker-compose.yml and restart: + ```bash + docker-compose down + # Edit docker-compose.yml to update image version + docker-compose up -d + ``` + +## Troubleshooting + +- **Connection Issues**: Ensure ports are not blocked by firewall +- **Performance Issues**: Check PostgreSQL logs with `docker-compose logs postgres` +- **Resource Problems**: Monitor container resource usage and adjust limits if needed + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/config/postgres-dev.conf b/config/postgres-dev.conf new file mode 100644 index 0000000..702a4ce --- /dev/null +++ b/config/postgres-dev.conf @@ -0,0 +1,62 @@ +# PostgreSQL Development Configuration File + +# CONNECTIONS AND AUTHENTICATION +listen_addresses = '*' +max_connections = 50 +password_encryption = scram-sha-256 +ssl = off + +# RESOURCE USAGE +shared_buffers = 64MB +work_mem = 4MB +maintenance_work_mem = 32MB +effective_cache_size = 1GB +max_worker_processes = 4 +max_parallel_workers_per_gather = 1 +max_parallel_workers = 4 + +# WRITE-AHEAD LOG +wal_level = minimal +max_wal_size = 512MB +min_wal_size = 40MB +checkpoint_timeout = 5min +checkpoint_completion_target = 0.9 + +# QUERY TUNING +random_page_cost = 4.0 +effective_io_concurrency = 1 +default_statistics_target = 100 + +# LOGGING +log_destination = 'stderr' +logging_collector = on +log_directory = 'pg_log' +log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' +log_truncate_on_rotation = off +log_rotation_age = 1d +log_rotation_size = 10MB +log_min_duration_statement = 250 +log_checkpoints = on +log_connections = on +log_disconnections = on +log_duration = on +log_error_verbosity = verbose +log_line_prefix = '%m [%p] %q%u@%d ' +log_statement = 'all' + +# AUTOVACUUM +autovacuum = on +log_autovacuum_min_duration = 250 +autovacuum_max_workers = 2 +autovacuum_naptime = 1min +autovacuum_vacuum_threshold = 50 +autovacuum_analyze_threshold = 50 + +# CLIENT CONNECTION DEFAULTS +datestyle = 'iso, mdy' +timezone = 'UTC' +lc_messages = 'en_US.UTF-8' +lc_monetary = 'en_US.UTF-8' +lc_numeric = 'en_US.UTF-8' +lc_time = 'en_US.UTF-8' +default_text_search_config = 'pg_catalog.english' diff --git a/config/postgres-production.conf b/config/postgres-production.conf new file mode 100644 index 0000000..67e33c6 --- /dev/null +++ b/config/postgres-production.conf @@ -0,0 +1,75 @@ +# PostgreSQL Production Configuration File + +# CONNECTIONS AND AUTHENTICATION +listen_addresses = '*' +max_connections = 200 +password_encryption = scram-sha-256 +ssl = on +ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem' +ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' + +# RESOURCE USAGE +shared_buffers = 2GB +work_mem = 8MB +maintenance_work_mem = 256MB +effective_cache_size = 8GB +max_worker_processes = 12 +max_parallel_workers_per_gather = 4 +max_parallel_workers = 12 + +# WRITE-AHEAD LOG +wal_level = replica +max_wal_size = 2GB +min_wal_size = 1GB +checkpoint_timeout = 15min +checkpoint_completion_target = 0.9 +archive_mode = on +archive_command = 'test ! -f /var/lib/postgresql/archive/%f && cp %p /var/lib/postgresql/archive/%f' + +# REPLICATION +max_wal_senders = 10 +wal_keep_size = 1GB +hot_standby = on +hot_standby_feedback = on + +# QUERY TUNING +random_page_cost = 1.1 +effective_io_concurrency = 200 +default_statistics_target = 500 +jit = on + +# LOGGING +log_destination = 'stderr' +logging_collector = on +log_directory = 'pg_log' +log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' +log_truncate_on_rotation = off +log_rotation_age = 1d +log_rotation_size = 100MB +log_min_duration_statement = 1000 +log_checkpoints = on +log_connections = on +log_disconnections = on +log_duration = off +log_error_verbosity = default +log_line_prefix = '%m [%p] %q%u@%d ' +log_statement = 'none' + +# AUTOVACUUM +autovacuum = on +log_autovacuum_min_duration = 1000 +autovacuum_max_workers = 6 +autovacuum_naptime = 1min +autovacuum_vacuum_threshold = 50 +autovacuum_analyze_threshold = 50 +autovacuum_vacuum_scale_factor = 0.05 +autovacuum_analyze_scale_factor = 0.025 + +# CLIENT CONNECTION DEFAULTS +datestyle = 'iso, mdy' +timezone = 'UTC' +lc_messages = 'en_US.UTF-8' +lc_monetary = 'en_US.UTF-8' +lc_numeric = 'en_US.UTF-8' +lc_time = 'en_US.UTF-8' +default_text_search_config = 'pg_catalog.english' diff --git a/config/postgres-staging.conf b/config/postgres-staging.conf new file mode 100644 index 0000000..6d5c4b1 --- /dev/null +++ b/config/postgres-staging.conf @@ -0,0 +1,64 @@ +# PostgreSQL Staging Configuration File + +# CONNECTIONS AND AUTHENTICATION +listen_addresses = '*' +max_connections = 100 +password_encryption = scram-sha-256 +ssl = on +ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem' +ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' + +# RESOURCE USAGE +shared_buffers = 256MB +work_mem = 6MB +maintenance_work_mem = 64MB +effective_cache_size = 2GB +max_worker_processes = 6 +max_parallel_workers_per_gather = 2 +max_parallel_workers = 6 + +# WRITE-AHEAD LOG +wal_level = replica +max_wal_size = 1GB +min_wal_size = 80MB +checkpoint_timeout = 5min +checkpoint_completion_target = 0.9 + +# QUERY TUNING +random_page_cost = 4.0 +effective_io_concurrency = 2 +default_statistics_target = 100 + +# LOGGING +log_destination = 'stderr' +logging_collector = on +log_directory = 'pg_log' +log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' +log_truncate_on_rotation = off +log_rotation_age = 1d +log_rotation_size = 10MB +log_min_duration_statement = 500 +log_checkpoints = on +log_connections = on +log_disconnections = on +log_duration = off +log_error_verbosity = default +log_line_prefix = '%m [%p] %q%u@%d ' +log_statement = 'mod' + +# AUTOVACUUM +autovacuum = on +log_autovacuum_min_duration = 500 +autovacuum_max_workers = 3 +autovacuum_naptime = 1min +autovacuum_vacuum_threshold = 50 +autovacuum_analyze_threshold = 50 + +# CLIENT CONNECTION DEFAULTS +datestyle = 'iso, mdy' +timezone = 'UTC' +lc_messages = 'en_US.UTF-8' +lc_monetary = 'en_US.UTF-8' +lc_numeric = 'en_US.UTF-8' +lc_time = 'en_US.UTF-8' +default_text_search_config = 'pg_catalog.english' diff --git a/config/postgres.conf b/config/postgres.conf new file mode 100644 index 0000000..3fd1515 --- /dev/null +++ b/config/postgres.conf @@ -0,0 +1,65 @@ +# PostgreSQL Configuration File +# This is the main configuration file that will be used by default + +# CONNECTIONS AND AUTHENTICATION +listen_addresses = '*' +max_connections = 100 +password_encryption = scram-sha-256 +ssl = on +ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem' +ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' + +# RESOURCE USAGE +shared_buffers = 128MB +work_mem = 4MB +maintenance_work_mem = 64MB +effective_cache_size = 4GB +max_worker_processes = 8 +max_parallel_workers_per_gather = 2 +max_parallel_workers = 8 + +# WRITE-AHEAD LOG +wal_level = replica +max_wal_size = 1GB +min_wal_size = 80MB +checkpoint_timeout = 5min +checkpoint_completion_target = 0.9 + +# QUERY TUNING +random_page_cost = 4.0 +effective_io_concurrency = 2 +default_statistics_target = 100 + +# LOGGING +log_destination = 'stderr' +logging_collector = on +log_directory = 'pg_log' +log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' +log_truncate_on_rotation = off +log_rotation_age = 1d +log_rotation_size = 10MB +log_min_duration_statement = 1000 +log_checkpoints = on +log_connections = on +log_disconnections = on +log_duration = off +log_error_verbosity = default +log_line_prefix = '%m [%p] %q%u@%d ' +log_statement = 'none' + +# AUTOVACUUM +autovacuum = on +log_autovacuum_min_duration = 1000 +autovacuum_max_workers = 3 +autovacuum_naptime = 1min +autovacuum_vacuum_threshold = 50 +autovacuum_analyze_threshold = 50 + +# CLIENT CONNECTION DEFAULTS +datestyle = 'iso, mdy' +timezone = 'UTC' +lc_messages = 'en_US.UTF-8' +lc_monetary = 'en_US.UTF-8' +lc_numeric = 'en_US.UTF-8' +lc_time = 'en_US.UTF-8' +default_text_search_config = 'pg_catalog.english' diff --git a/docker-compose.override.yaml b/docker-compose.override.yaml new file mode 100644 index 0000000..6bd1f0d --- /dev/null +++ b/docker-compose.override.yaml @@ -0,0 +1,25 @@ +# This override file is for local development and is automatically loaded when running docker-compose up +# without specifying a different file + +services: + postgres: + ports: + - "5432:5432" + volumes: + - ./config/postgres-dev.conf:/etc/postgresql/postgresql.conf + environment: + POSTGRES_PASSWORD: postgres_dev_password + command: postgres -c config_file=/etc/postgresql/postgresql.conf + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + interval: 5s + timeout: 3s + retries: 3 + start_period: 10s + + pgadmin: + ports: + - "5050:80" + environment: + PGADMIN_DEFAULT_EMAIL: dev@example.com + PGADMIN_DEFAULT_PASSWORD: pgadmin_dev_password diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..3898238 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,51 @@ +services: + postgres: + image: postgres:15 # Using PostgreSQL 15 + container_name: postgres + restart: always # Ensures PostgreSQL restarts automatically + environment: + # Environment variables for authentication + - POSTGRES_USER=${POSTGRES_USER:-postgres} # Default: postgres + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} # Default: password + - POSTGRES_DB=${POSTGRES_DB:-postgres} # Default: postgres + - PGDATA=/var/lib/postgresql/data/pgdata + volumes: + # Persistent data storage + - postgres_data:/var/lib/postgresql/data # Database files + - ./config/postgres.conf:/etc/postgresql/postgresql.conf # Configuration file + - ./init:/docker-entrypoint-initdb.d # Initialization scripts + ports: + - "5432:5432" # Expose PostgreSQL port + command: postgres -c config_file=/etc/postgresql/postgresql.conf + healthcheck: + # Regular health checks + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres}", + ] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - postgres_network + ulimits: + # Increase file descriptor limits for production + nofile: + soft: 64000 + hard: 64000 + logging: + # Log rotation to prevent disk space issues + driver: "json-file" + options: + max-size: "200m" + max-file: "10" + +volumes: + postgres_data: # Persistent volume for database files + driver: local + +networks: + postgres_network: + driver: bridge diff --git a/environments/dev.yaml b/environments/dev.yaml new file mode 100644 index 0000000..9d58d1e --- /dev/null +++ b/environments/dev.yaml @@ -0,0 +1,33 @@ +version: '3.8' + +services: + postgres: + environment: + POSTGRES_USER: postgres_dev + POSTGRES_PASSWORD: postgres_dev_password + POSTGRES_DB: postgres_dev + ports: + - "5432:5432" + volumes: + - postgres_data_dev:/var/lib/postgresql/data + - ./config/postgres-dev.conf:/etc/postgresql/postgresql.conf + command: postgres -c config_file=/etc/postgresql/postgresql.conf + + pgadmin: + environment: + PGADMIN_DEFAULT_EMAIL: dev@example.com + PGADMIN_DEFAULT_PASSWORD: pgadmin_dev_password + ports: + - "5050:80" + volumes: + - pgadmin_data_dev:/var/lib/pgadmin + +volumes: + postgres_data_dev: + name: postgres_data_dev + pgadmin_data_dev: + name: pgadmin_data_dev + +networks: + postgres_network: + name: postgres_network_dev diff --git a/environments/production.yaml b/environments/production.yaml new file mode 100644 index 0000000..53432ad --- /dev/null +++ b/environments/production.yaml @@ -0,0 +1,82 @@ +version: '3.8' + +services: + postgres: + image: postgres:15-alpine + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "5432:5432" + volumes: + - postgres_data_prod:/var/lib/postgresql/data + - ./config/postgres-production.conf:/etc/postgresql/postgresql.conf + command: postgres -c config_file=/etc/postgresql/postgresql.conf + deploy: + resources: + limits: + cpus: '2' + memory: 4G + restart_policy: + condition: any + delay: 5s + max_attempts: 3 + window: 120s + logging: + driver: "json-file" + options: + max-size: "200m" + max-file: "10" + + pgadmin: + image: dpage/pgadmin4:latest + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} + PGADMIN_CONFIG_SERVER_MODE: 'True' + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'True' + ports: + - "5050:80" + volumes: + - pgadmin_data_prod:/var/lib/pgadmin + deploy: + resources: + limits: + cpus: '1' + memory: 1G + restart_policy: + condition: any + delay: 5s + max_attempts: 3 + window: 120s + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "5" + +volumes: + postgres_data_prod: + name: postgres_data_prod + driver: local + driver_opts: + type: 'none' + o: 'bind' + device: '/data/postgres' + pgadmin_data_prod: + name: pgadmin_data_prod + driver: local + driver_opts: + type: 'none' + o: 'bind' + device: '/data/pgadmin' + +networks: + postgres_network: + name: postgres_network_prod + driver: bridge + ipam: + driver: default + config: + - subnet: 172.28.0.0/16 diff --git a/environments/staging.yaml b/environments/staging.yaml new file mode 100644 index 0000000..46481e2 --- /dev/null +++ b/environments/staging.yaml @@ -0,0 +1,43 @@ +version: '3.8' + +services: + postgres: + environment: + POSTGRES_USER: postgres_staging + POSTGRES_PASSWORD: postgres_staging_password + POSTGRES_DB: postgres_staging + ports: + - "5433:5432" + volumes: + - postgres_data_staging:/var/lib/postgresql/data + - ./config/postgres-staging.conf:/etc/postgresql/postgresql.conf + command: postgres -c config_file=/etc/postgresql/postgresql.conf + deploy: + resources: + limits: + cpus: '1' + memory: 1G + + pgadmin: + environment: + PGADMIN_DEFAULT_EMAIL: staging@example.com + PGADMIN_DEFAULT_PASSWORD: pgadmin_staging_password + ports: + - "5051:80" + volumes: + - pgadmin_data_staging:/var/lib/pgadmin + deploy: + resources: + limits: + cpus: '0.5' + memory: 512M + +volumes: + postgres_data_staging: + name: postgres_data_staging + pgadmin_data_staging: + name: pgadmin_data_staging + +networks: + postgres_network: + name: postgres_network_staging diff --git a/init/01-init.sql b/init/01-init.sql new file mode 100644 index 0000000..21cacc4 --- /dev/null +++ b/init/01-init.sql @@ -0,0 +1,84 @@ +-- PostgreSQL Initialization Script +-- This script will be executed when the PostgreSQL container is first created + +-- Create extensions +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pg_stat_statements"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- Create roles +CREATE ROLE readonly; +GRANT CONNECT ON DATABASE postgres TO readonly; +GRANT USAGE ON SCHEMA public TO readonly; +GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly; + +CREATE ROLE readwrite; +GRANT CONNECT ON DATABASE postgres TO readwrite; +GRANT USAGE, CREATE ON SCHEMA public TO readwrite; +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite; +GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO readwrite; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE ON SEQUENCES TO readwrite; + +-- Create sample users +CREATE USER sample_readonly WITH PASSWORD 'readonly_password'; +GRANT readonly TO sample_readonly; + +CREATE USER sample_readwrite WITH PASSWORD 'readwrite_password'; +GRANT readwrite TO sample_readwrite; + +-- Create sample schema and tables +CREATE SCHEMA IF NOT EXISTS sample; + +CREATE TABLE IF NOT EXISTS sample.users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100) NOT NULL UNIQUE, + password_hash VARCHAR(100) NOT NULL, + first_name VARCHAR(50), + last_name VARCHAR(50), + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS sample.posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES sample.users(id) ON DELETE CASCADE, + title VARCHAR(200) NOT NULL, + content TEXT, + is_published BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Create indexes +CREATE INDEX IF NOT EXISTS idx_users_username ON sample.users(username); +CREATE INDEX IF NOT EXISTS idx_users_email ON sample.users(email); +CREATE INDEX IF NOT EXISTS idx_posts_user_id ON sample.posts(user_id); +CREATE INDEX IF NOT EXISTS idx_posts_created_at ON sample.posts(created_at); + +-- Insert sample data +INSERT INTO sample.users (username, email, password_hash, first_name, last_name) +VALUES + ('johndoe', 'john.doe@example.com', crypt('password123', gen_salt('bf')), 'John', 'Doe'), + ('janedoe', 'jane.doe@example.com', crypt('password123', gen_salt('bf')), 'Jane', 'Doe') +ON CONFLICT (username) DO NOTHING; + +-- Get user IDs for sample posts +DO $$ +DECLARE + john_id UUID; + jane_id UUID; +BEGIN + SELECT id INTO john_id FROM sample.users WHERE username = 'johndoe'; + SELECT id INTO jane_id FROM sample.users WHERE username = 'janedoe'; + + INSERT INTO sample.posts (user_id, title, content, is_published) + VALUES + (john_id, 'First Post', 'This is my first post content.', TRUE), + (john_id, 'Second Post', 'This is my second post content.', FALSE), + (jane_id, 'Hello World', 'Hello world post content.', TRUE) + ON CONFLICT DO NOTHING; +END $$; diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..b2c3604 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# PostgreSQL Backup Script for Proxmox LXC Container + +# Load environment variables +if [ -f ../.env ]; then + source ../.env +else + echo "Error: .env file not found!" + exit 1 +fi + +# Set default values if not provided in .env +BACKUP_DIR=${BACKUP_DIR:-"../backups"} +POSTGRES_USER=${POSTGRES_USER:-"postgres"} +POSTGRES_DB=${POSTGRES_DB:-"postgres"} +BACKUP_RETENTION_DAYS=${BACKUP_RETENTION_DAYS:-7} + +# Create backup directory if it doesn't exist +mkdir -p ${BACKUP_DIR} + +# Generate timestamp for backup file +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") +BACKUP_FILE="${BACKUP_DIR}/postgres_backup_${TIMESTAMP}.sql.gz" + +echo "Starting PostgreSQL backup..." +echo "Database: ${POSTGRES_DB}" +echo "Backup file: ${BACKUP_FILE}" + +# Perform the backup +docker exec -t postgres pg_dump -U ${POSTGRES_USER} -d ${POSTGRES_DB} | gzip > ${BACKUP_FILE} + +# Check if backup was successful +if [ $? -eq 0 ]; then + echo "Backup completed successfully: ${BACKUP_FILE}" + + # Remove backups older than retention period + find ${BACKUP_DIR} -name "postgres_backup_*.sql.gz" -type f -mtime +${BACKUP_RETENTION_DAYS} -delete + echo "Cleaned up old backups (older than ${BACKUP_RETENTION_DAYS} days)" +else + echo "Error: Backup failed!" + exit 1 +fi + +# Print backup information +echo "Backup size: $(du -h ${BACKUP_FILE} | cut -f1)" +echo "Available backups:" +ls -lh ${BACKUP_DIR} + +# Create a full volume backup as well (for disaster recovery) +echo "Creating volume backup..." +VOLUME_BACKUP_FILE="${BACKUP_DIR}/postgres_volume_backup_${TIMESTAMP}.tar.gz" +docker run --rm -v postgres_data:/data -v $(pwd)/${BACKUP_DIR}:/backup alpine tar -czf /backup/$(basename ${VOLUME_BACKUP_FILE}) /data + +if [ $? -eq 0 ]; then + echo "Volume backup completed successfully: ${VOLUME_BACKUP_FILE}" +else + echo "Warning: Volume backup failed!" +fi + +exit 0 diff --git a/scripts/monitor.sh b/scripts/monitor.sh new file mode 100644 index 0000000..68536fd --- /dev/null +++ b/scripts/monitor.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# PostgreSQL Monitoring Script for Proxmox LXC Container + +# Load environment variables +if [ -f ../.env ]; then + source ../.env +else + echo "Error: .env file not found!" + exit 1 +fi + +# Set default values if not provided in .env +POSTGRES_USER=${POSTGRES_USER:-"postgres"} +POSTGRES_DB=${POSTGRES_DB:-"postgres"} + +echo "PostgreSQL Service Monitoring for Proxmox LXC" +echo "==========================================" +echo + +# Check container status +echo "Container Status:" +docker ps --filter "name=postgres" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" +echo + +# Check PostgreSQL service health +echo "PostgreSQL Health Check:" +docker exec postgres pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} +if [ $? -eq 0 ]; then + echo "PostgreSQL is accepting connections" +else + echo "PostgreSQL is not accepting connections" +fi +echo + +# Database size information +echo "Database Size Information:" +docker exec postgres psql -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c "SELECT datname, pg_size_pretty(pg_database_size(datname)) as size FROM pg_database ORDER BY pg_database_size(datname) DESC;" +echo + +# Connection information +echo "Current Connections:" +docker exec postgres psql -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c "SELECT datname, count(*) as connections FROM pg_stat_activity GROUP BY datname;" +echo + +# Active queries +echo "Active Queries (running for more than 5 seconds):" +docker exec postgres psql -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c "SELECT pid, datname, usename, application_name, client_addr, state, now() - query_start as duration, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '5 seconds';" +echo + +# Resource usage +echo "Container Resource Usage:" +docker stats postgres --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" +echo + +# Disk usage +echo "Disk Usage:" +df -h $(docker volume inspect -f '{{ .Mountpoint }}' postgres_data) +echo + +# System resource usage +echo "System Resource Usage:" +echo "CPU:" +top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}' +echo "Memory:" +free -m | awk 'NR==2{printf "Used: %s MB (%.2f%%)", $3, $3*100/$2 }' +echo + +exit 0 diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100644 index 0000000..ac8b5a1 --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# PostgreSQL Restore Script for Proxmox LXC Container + +# Check if backup file is provided +if [ $# -ne 1 ]; then + echo "Usage: $0 " + echo "Example: $0 ../backups/postgres_backup_20250419_120000.sql.gz" + echo " $0 ../backups/postgres_volume_backup_20250419_120000.tar.gz" + exit 1 +fi + +BACKUP_FILE=$1 + +# Check if backup file exists +if [ ! -f "${BACKUP_FILE}" ]; then + echo "Error: Backup file '${BACKUP_FILE}' not found!" + exit 1 +fi + +# Load environment variables +if [ -f ../.env ]; then + source ../.env +else + echo "Error: .env file not found!" + exit 1 +fi + +# Set default values if not provided in .env +POSTGRES_USER=${POSTGRES_USER:-"postgres"} +POSTGRES_DB=${POSTGRES_DB:-"postgres"} + +echo "Starting PostgreSQL restore..." +echo "Database: ${POSTGRES_DB}" +echo "Backup file: ${BACKUP_FILE}" + +# Determine backup type (SQL dump or volume backup) +if [[ "${BACKUP_FILE}" == *"postgres_backup_"* ]]; then + # SQL dump restore + # Confirm restore operation + read -p "Warning: This will overwrite the existing database. Continue? (y/n): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Restore operation cancelled." + exit 0 + fi + + # Perform the restore + echo "Restoring database from SQL dump..." + gunzip -c ${BACKUP_FILE} | docker exec -i postgres psql -U ${POSTGRES_USER} -d ${POSTGRES_DB} + + # Check if restore was successful + if [ $? -eq 0 ]; then + echo "Restore completed successfully from: ${BACKUP_FILE}" + else + echo "Error: Restore failed!" + exit 1 + fi +elif [[ "${BACKUP_FILE}" == *"postgres_volume_backup_"* ]]; then + # Volume backup restore + # Confirm restore operation + read -p "WARNING: This will completely replace the PostgreSQL data volume. All current data will be lost. Continue? (y/n): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Restore operation cancelled." + exit 0 + fi + + echo "Stopping PostgreSQL container..." + docker-compose down + + echo "Restoring volume data from backup..." + # Create a temporary container to restore the volume + docker run --rm -v postgres_data:/data -v $(pwd)/$(dirname ${BACKUP_FILE}):/backup alpine sh -c "rm -rf /data/* && tar -xzf /backup/$(basename ${BACKUP_FILE}) -C / --strip-components=1" + + # Check if restore was successful + if [ $? -eq 0 ]; then + echo "Volume restore completed successfully from: ${BACKUP_FILE}" + echo "Starting PostgreSQL container..." + docker-compose up -d + echo "Waiting for PostgreSQL to start..." + sleep 10 + echo "PostgreSQL service restored and running." + else + echo "Error: Volume restore failed!" + echo "Starting PostgreSQL container..." + docker-compose up -d + exit 1 + fi +else + echo "Error: Unknown backup file format. Expected postgres_backup_*.sql.gz or postgres_volume_backup_*.tar.gz" + exit 1 +fi + +exit 0 diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100644 index 0000000..d6996e6 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Setup script for PostgreSQL service + +# Make scripts executable +chmod +x ./backup.sh +chmod +x ./restore.sh +chmod +x ./monitor.sh + +# Create necessary directories +mkdir -p ../backups +mkdir -p ../logs + +# Copy environment file if it doesn't exist +if [ ! -f ../.env ]; then + cp ../.env.example ../.env + echo "Created .env file from .env.example" + echo "Please update the .env file with your configuration" +fi + +echo "Setup completed successfully!" +echo "You can now start the PostgreSQL service with: docker-compose up -d"