From e472bfd10826ac2205fcd90285c768e45afa0914 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 2 Mar 2026 12:32:01 +0100 Subject: [PATCH] update docker --- DOCKER.md | 14 +++++++++++++- Dockerfile | 19 ++++++++++--------- docker-compose-prod.yml | 32 ++++++++++++++++++++++++++++++++ docker-compose.yml | 14 +++++++------- docker-entrypoint.sh | 19 +++++++++++++++++++ internal/config/config.go | 30 +++++++++++++++++++----------- 6 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 docker-compose-prod.yml create mode 100644 docker-entrypoint.sh diff --git a/DOCKER.md b/DOCKER.md index 0031e44..c796253 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -188,7 +188,7 @@ docker buildx build --platform linux/amd64,linux/arm64 -t gitcitadel-online . For production deployment: -1. **Use a reverse proxy** (nginx, Traefik, etc.) in front of the container +1. **Use a reverse proxy** (nginx, Traefik, Apache, etc.) in front of the container 2. **Set up SSL/TLS** certificates 3. **Configure proper logging** and monitoring 4. **Use secrets management** for sensitive configuration @@ -201,6 +201,18 @@ For production deployment: memory: 512M ``` +## Apache Reverse Proxy Setup + +The `docker-compose.yml` is configured to expose the container on port **2323** for Apache reverse proxy integration. + +### Port Configuration + +The Docker container exposes port 2323 on the host, which maps to port 8080 inside the container. This matches Apache configurations that proxy to `127.0.0.1:2323`. + +If you need to use a different port, update `docker-compose.yml`: Change `"2323:8080"` to your desired port mapping. + +**Note:** For Plesk-managed Apache servers, configure the reverse proxy settings through the Plesk control panel. The Docker container is ready to accept connections on port 2323. + ## Updating To update to a new version: diff --git a/Dockerfile b/Dockerfile index 83fcfc5..5a2950a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,16 +54,17 @@ RUN if [ ! -f ./static/js/nostr.bundle.js ]; then \ # Copy example config (user should mount their own config.yaml) COPY config.yaml.example ./config.yaml.example -# Create cache directories -RUN mkdir -p cache/media +# Copy entrypoint script +COPY docker-entrypoint.sh /app/docker-entrypoint.sh # Create non-root user for security -RUN addgroup -g 1000 appuser && \ - adduser -D -u 1000 -G appuser appuser && \ - chown -R appuser:appuser /app +# node:20-alpine already has a 'node' user with UID 1000 +# Change ownership of /app to node user +RUN chown -R node:node /app && \ + chmod +x /app/docker-entrypoint.sh # Switch to non-root user -USER appuser +USER node # Expose port (default 8080, can be overridden via config) EXPOSE 8080 @@ -72,6 +73,6 @@ EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 -# Run the application -ENTRYPOINT ["/app/gitcitadel-online"] -CMD ["--config", "/app/config.yaml"] +# Run the application via entrypoint script +ENTRYPOINT ["/app/docker-entrypoint.sh"] +CMD ["/app/gitcitadel-online", "--config", "/app/config.yaml"] diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml new file mode 100644 index 0000000..1db487c --- /dev/null +++ b/docker-compose-prod.yml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + gitcitadel-online: + image: silberengel/gitcitadel-online:latest + container_name: gitcitadel-online + restart: unless-stopped + ports: + # Expose port 2323 for Apache reverse proxy (maps to container port 8080) + - "2323:8080" + volumes: + # Persist cache directory + # Note: Ensure the host cache directory is writable by UID 1000 (node user) + # Run: sudo chown -R 1000:1000 ./cache (or use 777 permissions) + - ./cache:/app/cache + # Optional: Mount config file to override defaults + # - ./config.yaml:/app/config.yaml:ro + # Optional environment variables (uncomment and set as needed): + # environment: + # - CONFIG_PATH=/app/config.yaml + # - LOG_LEVEL=info + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + deploy: + resources: + limits: + cpus: '1' + memory: 512M diff --git a/docker-compose.yml b/docker-compose.yml index ff69033..4fea9a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,8 @@ services: container_name: gitcitadel-online restart: unless-stopped ports: - - "8080:8080" + # Expose port 2323 for Apache reverse proxy (maps to container port 8080) + - "2323:8080" volumes: # Mount config file (create from config.yaml.example) - ./config.yaml:/app/config.yaml:ro @@ -19,15 +20,14 @@ services: # - CONFIG_PATH=/app/config.yaml # Optional: set log level # - LOG_LEVEL=info - networks: - - gitcitadel-network healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s - -networks: - gitcitadel-network: - driver: bridge + deploy: + resources: + limits: + cpus: '1' + memory: 512M \ No newline at end of file diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..d0ece12 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -e + +# Ensure cache/media directory exists and is writable +# This handles both mounted volumes and container-internal directories +if [ ! -d "cache/media" ]; then + mkdir -p cache/media || { + echo "Error: Failed to create cache/media directory. Check permissions." >&2 + exit 1 + } +fi + +# Ensure the cache directory is writable +if [ ! -w "cache" ] || [ ! -w "cache/media" ]; then + echo "Warning: Cache directory may not be writable. Check permissions on mounted volume." >&2 +fi + +# Execute the main application +exec "$@" diff --git a/internal/config/config.go b/internal/config/config.go index 94c460d..0eb4495 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -39,9 +39,16 @@ type Config struct { } // LoadConfig loads configuration from a YAML file +// If the file doesn't exist, returns a config with all defaults func LoadConfig(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { + if os.IsNotExist(err) { + // File doesn't exist, return config with all defaults + config := &Config{} + setDefaults(config) + return config, nil + } return nil, fmt.Errorf("failed to read config file: %w", err) } @@ -50,7 +57,18 @@ func LoadConfig(path string) (*Config, error) { return nil, fmt.Errorf("failed to parse config file: %w", err) } - // Set defaults + setDefaults(&config) + return &config, nil +} + +// setDefaults applies default values to a config struct +func setDefaults(config *Config) { + if config.WikiIndex == "" { + config.WikiIndex = "naddr1qvzqqqr4tqpzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyd8wumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6qgmwaehxw309a6xsetrd96xzer9dshxummnw3erztnrdakszyrhwden5te0dehhxarj9ekxzmnyqyg8wumn8ghj7mn0wd68ytnhd9hx2qghwaehxw309ahx7um5wgh8xmmkvf5hgtngdaehgqg3waehxw309ahx7um5wgerztnrdaksz9thwden5te0v9nkwu3wdehhxarj9ekxzmnyqyv8wumn8ghj7un9d3shjtnwdaehw6r9wfjjucm0d5q3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgewaehxw309an8yet9d3shjtnndamxy6t59e5x7um5qqhxw6t5vd5hgctyv4kz6urjda4x2cm594jx7cm4d4jkuarpw35k7m3dvfuj6um5v4kxccfdwcknzhekhth" + } + if config.BlogIndex == "" { + config.BlogIndex = "naddr1qvzqqqr4tqpzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyd8wumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6qgmwaehxw309a6xsetrd96xzer9dshxummnw3erztnrdakszyrhwden5te0dehhxarj9ekxzmnyqyg8wumn8ghj7mn0wd68ytnhd9hx2qghwaehxw309ahx7um5wgh8xmmkvf5hgtngdaehgqg3waehxw309ahx7um5wgerztnrdaksz9thwden5te0v9nkwu3wdehhxarj9ekxzmnyqyv8wumn8ghj7un9d3shjtnwdaehw6r9wfjjucm0d5q3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgewaehxw309an8yet9d3shjtnndamxy6t59e5x7um5qqshg6r994nkjarrd96xzer9dskkymr0vukky7fdwd6x2mrvvykhvtf3q3js44" + } if config.Relays.Feeds == "" { config.Relays.Feeds = "wss://theforest.nostr1.com" } @@ -87,13 +105,6 @@ func LoadConfig(path string) (*Config, error) { if config.SEO.DefaultImage == "" { config.SEO.DefaultImage = "/static/GitCitadel_Graphic_Landscape.png" } - - // Validate required fields - if config.WikiIndex == "" { - return nil, fmt.Errorf("wiki_index is required") - } - - return &config, nil } // parseRelayList parses a comma-separated relay string into a slice @@ -126,9 +137,6 @@ func (c *Config) GetContactFormRelays() []string { // Validate validates the configuration func (c *Config) Validate() error { - if c.WikiIndex == "" { - return fmt.Errorf("wiki_index is required") - } if c.Relays.Feeds == "" { return fmt.Errorf("relays.feeds is required") }