• Dec. 5, 2025, 5:21 a.m.

    1. Overview

    Ente is an end-to-end encrypted alternative to Google Photos. While YunoHost does not have a native Ente app, it can be hosted efficiently using Docker Compose.

    Key Challenges Solved in this Guide:

    • Configuring SMTP for OTP-based login (Hostinger).
    • Resolving "Incorrect Code" errors due to server time drift.
    • Overcoming the default 10GB storage quota.
    • Managing MinIO (S3) bucket conflicts during reinstallations.

    2. Preparation

    Directory Setup

    Create a dedicated directory for the project to ensure data persistence outside the containers.

    mkdir -p /home/user/ente/data
    cd /home/user/ente
    

    Generating Security Keys

    Ente requires three specific cryptographic keys. Run these commands using openssl and save the outputs. You will need them for the configuration file.

    # 1. JWT Secret (for authentication)
    openssl rand -base64 32
    
    # 2. Key Encryption Key (for admin keys)
    openssl rand -base64 32
    
    # 3. Hash Key (for passwords)
    openssl rand -base64 32
    

    3. Configuration

    A. The Server Config (museum.yaml)

    Create museum.yaml. This file defines the database connection, storage backend, and email settings.

    • Critical Note: We use Port 465 (SSL) for Hostinger SMTP to ensure reliable email delivery.
    • Replace: The jwt and key sections with the strings you generated above.
    http:
      port: 8080
      use_https: false # Nginx (YunoHost) handles SSL
    
    db:
      host: postgres
      port: 5432
      name: ente_db
      user: pguser
      password: pgpass 
      sslmode: disable
    
    # SMTP Settings (Hostinger Example)
    smtp:
      host: "smtp.hostinger.com"
      port: 465
      username: "your@email.here"
      password: "YOUR_EMAIL_PASSWORD"
      encryption: "ssl"
      email: "your@email.here"
      sender_name: "Ente Photos"
    
    # Internal MinIO (S3) configuration
    s3:
      are_local_buckets: true
      b2-eu-cen:
        key: minioadmin
        secret: minioadmin
        endpoint: http://minio:9000
        region: eu-central-1
        bucket: ente-photos
      wasabi-eu-central-2-v3:
        key: minioadmin
        secret: minioadmin
        endpoint: http://minio:9000
        region: eu-central-1
        bucket: ente-videos
    
    # Security Keys (Replace with your OpenSSL outputs)
    jwt:
      secret: "REPLACE_WITH_YOUR_JWT_SECRET"
    key:
      encryption: "REPLACE_WITH_YOUR_ENCRYPTION_KEY"
      hash: "REPLACE_WITH_YOUR_HASH_KEY"
    
    internal:
      admins: []
    

    B. The Docker Map (compose.yaml)

    Create compose.yaml.

    • Critical Fix: We mount /etc/localtime and set TZ=Asia/Kuala_Lumpur. Without this, the server defaults to UTC or an incorrect timezone, causing OTP codes to fail immediately.
    services:
      # The Ente Server (API)
      museum:
        image: ghcr.io/ente-io/server:latest
        container_name: ente_museum
        restart: unless-stopped
        ports:
          - "54752:8080" # Mapped to specific host port
        volumes:
          - ./museum.yaml:/museum.yaml:ro
          - ./data/logs:/var/logs
          - ./data/museum-data:/var/lib/museum
          # SYNC HOST TIME TO CONTAINER
          - /etc/localtime:/etc/localtime:ro
          - /etc/timezone:/etc/timezone:ro
        depends_on:
          postgres:
            condition: service_healthy
          minio:
            condition: service_started
        environment:
          - ENTE_DB_PASSWORD=pgpass
          - TZ=Asia/Kuala_Lumpur # Forces correct OTP generation time
    
      # Database
      postgres:
        image: postgres:16
        container_name: ente_postgres
        restart: unless-stopped
        environment:
          POSTGRES_USER: pguser
          POSTGRES_PASSWORD: pgpass
          POSTGRES_DB: ente_db
        volumes:
          - ./data/postgres:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U pguser -d ente_db"]
          interval: 5s
          timeout: 5s
          retries: 5
    
      # Object Storage (MinIO)
      minio:
        image: minio/minio
        container_name: ente_minio
        restart: unless-stopped
        command: server /data --console-address ":9001"
        environment:
          MINIO_ROOT_USER: minioadmin
          MINIO_ROOT_PASSWORD: minioadmin
        volumes:
          - ./data/minio:/data
    
      # Automatic Bucket Provisioner
      minio-provision:
        image: minio/mc
        depends_on:
          - minio
        entrypoint: >
          /bin/sh -c "
          until mc alias set local http://minio:9000 minioadmin minioadmin; do echo 'Waiting for MinIO...'; sleep 1; done;
          mc mb local/ente-photos;
          mc mb local/ente-videos;
          exit 0;
          "
    

    4. Deployment & Network

    1. Launch the stack:

      bash docker compose up -d

    2. YunoHost Configuration:

      • Go to YunoHost Admin > Applications > Install Custom Webapp.
      • Domain: ente.tcp-ip.my
      • Path: /
      • Destination: http://127.0.0.1:54752

    5. The "Incorrect Code" Fix (Crucial)

    If you receive an "Incorrect Code" error during signup, it is likely a time synchronisation issue.

    1. Check Host Time: Ensure the physical server has NTP enabled and the correct timezone.
      bash sudo timedatectl set-timezone Asia/Kuala_Lumpur sudo timedatectl set-ntp true
    2. The "One Click" Rule: When signing up on the mobile app, tap "Send" once. Do not tap "Resend" immediately. Wait 2–3 minutes for the email. Clicking twice invalidates the first code.

    6. Post-Install: Unlocking Storage Quota

    By default, self-hosted users are limited to 10GB. To increase this (e.g., to 32GB + 10GB Base), you must modify the database.

    1. Enter the Database Container:

      bash docker exec -it ente_postgres psql -U pguser -d ente_db

    2. Insert Storage Bonus:
      Run this SQL command to add 32GB (in bytes) and set the expiration to the year 2100 (to fix the "1970" display bug).

      sql INSERT INTO storage_bonus (bonus_id, user_id, storage, type, valid_till) VALUES ( 'manual-32gb', (SELECT user_id FROM users LIMIT 1), 34359738368, 'ADD_ON_SUPPORT', 4102444800000000 );

      (Note: 34359738368 bytes = 32GB. 4102444800000000 microseconds = Year 2100).

    3. Verify: Refresh the mobile app. You should see "42 GB Total".


    7. Troubleshooting: The "Nuclear Wipe"

    If you need to restart the installation (e.g., after changing keys or fixing a corrupted database), you must wipe both the database and the MinIO storage. If you only wipe the DB, the new installation will crash (Error 500) because the storage buckets already exist.

    The Clean Reset Command:

    docker compose down
    sudo rm -rf ./data  # Deletes DB, MinIO, and Logs
    docker compose up -d
    
  • edit

    Thread title has been changed from Ente: Media Storage - Docker.