├── .env.example ├── .github └── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── .gitignore ├── FAQs.md ├── README.md ├── config └── nginx.conf ├── docker-compose.yml ├── gen-passwords.sh └── setup.sh /.env.example: -------------------------------------------------------------------------------- 1 | # 2 | # Frontend configuration. 3 | # 4 | 5 | # If the user should be required to login before doing anything. 6 | # Setting this to true will allow the user to "try out" the app without having to log in. 7 | OCULAR_DEMO=false 8 | 9 | # 10 | # Backend configuration. 11 | # 12 | 13 | # Database location 14 | GENESIS_DB_PATH=.data 15 | 16 | # JWT secret known only to your token generator 17 | GENESIS_JWT_SECRET= 18 | 19 | # JWT expiration in minutes 20 | GENESIS_JWT_TOKEN_EXPIRATION=120960 21 | 22 | # If the session cookie for the backend should be allowed to be sent over http 23 | # Dangerous, it's best to run it behind a reverse proxy with https 24 | GENESIS_JWT_COOKIE_ALLOW_HTTP=false 25 | 26 | # Gin mode, either test, release or debug 27 | GENESIS_GIN_MODE=release 28 | 29 | # Zap loggger, either production or development 30 | GENESIS_LOG_MODE=production 31 | 32 | # Port to listen on, leave it at 80 if you're using a reverse proxy 33 | GENESIS_PORT=80 34 | 35 | # Base url to listen for requests 36 | GENESIS_BASE_URL=/ 37 | 38 | # Use ! as suffix for the username to indicate that this user 39 | # should be created as an admin. These can add, remove and edit users. 40 | GENESIS_CREATE_USERS=admin!:2lWK6m4hgmxjUGHo 41 | 42 | # Allowed username pattern 43 | GENESIS_USERNAME_PATTERN=^[\w]{0,32}$ 44 | 45 | # Allowed key pattern 46 | GENESIS_KEY_PATTERN=^[\w]{0,32}$ 47 | 48 | # Maximum size of each key in kilobytes 49 | GENESIS_DATA_MAX_SIZE=512 50 | 51 | # Maximum amount of datasets per user 52 | GENESIS_KEYS_PER_USER=2 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug in the docker setup for Ocular 3 | body: 4 | - type: checkboxes 5 | attributes: 6 | label: Support guidelines 7 | description: Please read the faqs before proceeding. 8 | options: 9 | - label: I've read the [FAQs](https://github.com/simonwep/ocular-docker?tab=readme-ov-file#faq) 10 | required: true 11 | 12 | - type: textarea 13 | attributes: 14 | label: Description 15 | description: Please provide a brief description of the bug in 1-2 sentences. 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | attributes: 21 | label: Environment 22 | description: Please provide information about your environment. 23 | placeholder: | 24 | - Browser: [e.g. Chrome, Safari] 25 | - OS: [e.g. Windows, macOS] 26 | - Version: [e.g. 1.2] 27 | - Docker version: [e.g. 1.4] 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | attributes: 33 | label: Expected behaviour 34 | description: Please describe precisely what you'd expect to happen. Be specific. 35 | validations: 36 | required: false 37 | 38 | - type: textarea 39 | attributes: 40 | label: Steps to reproduce 41 | description: Please describe the steps to reproduce the bug. 42 | placeholder: | 43 | 1. ... 44 | 2. ... 45 | 3. ... 46 | validations: 47 | required: false 48 | 49 | - type: textarea 50 | attributes: 51 | label: Additional info 52 | description: Please provide any additional information that seem useful. 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/simonwep/ocular/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .env 3 | .env.bak 4 | data -------------------------------------------------------------------------------- /FAQs.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | > [!NOTE] 4 | > Since this setup is fairly new, there might be some issues that are not covered here. 5 | 6 | This is a compilation of frequently asked questions and their answers. 7 | If you have a question that is not answered here, feel free to [open a discussion](https://github.com/simonwep/ocular-docker/discussions)! 8 | 9 | ## Table of Contents 10 | 11 | - [Where can I find the release notes?](#where-can-i-find-the-release-notes) 12 | - [I can't log in to the app over the network!](#i-cant-log-in-to-the-app-over-the-network) 13 | - [What kind of config do I need if I want to run it behind a nginx reverse proxy?](#what-kind-of-config-do-i-need-if-i-want-to-run-it-behind-a-nginx-reverse-proxy) 14 | - [I'm having troubles deploying it on Traefik](#im-having-troubles-deploying-it-on-traefik) 15 | - [Should I use nginx in the docker compose.yaml file if I already have my own reverse proxy already set up?](#should-i-use-nginx-in-the-docker-composeyaml-file-if-i-already-have-my-own-reverse-proxy-already-set-up) 16 | - [When reverse proxying Ocular, which Docker service do I point to: frontend, backend, or nginx?](#when-reverse-proxying-ocular-which-docker-service-do-i-point-to-frontend-backend-or-nginx) 17 | 18 | ## Where can I find the release notes? 19 | 20 | For release notes, check out the [latest release](https://github.com/simonwep/ocular/releases/latest) in the [ocular](https://github.com/simonwep/ocular) repository. 21 | This repo is just for production releases :) 22 | 23 | ## I can't log in to the app over the network! 24 | 25 | If you don't use https, make sure to set `GENESIS_JWT_COOKIE_ALLOW_HTTP` to `true` in your `.env` file. 26 | Otherwise, run it behind a reverse proxy like [nginx](https://www.nginx.com/) and get a free certificate from [letsencrypt](https://letsencrypt.org/). 27 | 28 | Make sure to restart the app after changing the `.env` file via `docker compose restart`. 29 | 30 | ## What kind of config do I need if I want to run it behind a nginx reverse proxy? 31 | 32 | Here's an example of a basic nginx config (v1.25+): 33 | 34 | ```nginx 35 | server { 36 | listen 443 quic reuseport; 37 | listen 443 ssl; 38 | 39 | server_name ocular.example.com; 40 | add_header Alt-Svc 'h3=":443"; ma=86400'; 41 | 42 | location / { 43 | proxy_set_header X-Forwarded-Host $host; 44 | proxy_set_header X-Forwarded-Server $host; 45 | proxy_set_header X-Forwarded-Proto $scheme; 46 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 47 | proxy_set_header Host $host; 48 | proxy_read_timeout 300s; 49 | proxy_pass http://127.0.0.1:3030$request_uri; 50 | } 51 | 52 | # Specify the path to your certificate and key, or use letsencrypt 53 | #ssl_certificate 54 | #ssl_certificate_key 55 | } 56 | 57 | # HTTP Redirect 58 | server { 59 | listen 80; 60 | server_name ocular.example.com; 61 | 62 | if ($host = ocular.example.com) { 63 | return 301 https://$host$request_uri; 64 | } 65 | 66 | return 404; 67 | } 68 | ``` 69 | 70 | ## I'm having troubles deploying it on [Traefik](https://traefik.io/traefik/) 71 | 72 | > As mentioned [here](https://github.com/simonwep/ocular-docker/issues/5#issuecomment-2535524284) of [#5](https://github.com/simonwep/ocular-docker/issues/5) by @CompeyDev. 73 | 74 | Required changes to make this work: 75 | 76 | 1. Backend (genesis) container: 77 | ```yml 78 | - "traefik.enable=true" 79 | - "traefik.http.routers.genesis.rule=Host(`ocular.example.com`) && PathPrefix(`/api`)" 80 | # Important: This is what took a bit to figure out; we want to remove the `/api` from the 81 | # request before forwarding it, otherwise the backend would get a request on `/api`, which 82 | # would not work, as it expects requests to / by default. An alternative would be to set 83 | # GENESIS_BASE_URL to `/api` 84 | - "traefik.http.middlewares.strip-prefix.stripprefix.prefixes=/api" 85 | - "traefik.http.routers.genesis.middlewares=strip-prefix" 86 | # The entrypoint and TLS here are mandatory, see https://community.traefik.io/t/different-container-behind-and-api-how/7622 87 | - "traefik.http.routers.genesis.entrypoints=https" 88 | - "traefik.http.routers.genesis.tls=true" 89 | - "traefik.http.routers.genesis.tls.certresolver=letsencrypt" 90 | - "traefik.http.routers.genesis.service=genesis-service" 91 | - "traefik.http.services.genesis-service.loadbalancer.server.port=3031" 92 | ``` 93 | 94 | 2. Frontend (ocular) container: 95 | ```yml 96 | - "traefik.enable=true" 97 | - "traefik.http.routers.ocular.rule=Host(`ocular.example.com`)" 98 | - "traefik.http.routers.ocular.entrypoints=https" 99 | - "traefik.http.routers.ocular.tls=true" 100 | - "traefik.http.routers.ocular.tls.certresolver=letsencrypt" 101 | - "traefik.http.routers.ocular.service=ocular-service" 102 | - "traefik.http.services.ocular-service.loadbalancer.server.port=80" 103 | ``` 104 | 105 | > [!NOTE] 106 | > Traefik prioritizes routers based on the length of the rule, so since the `genesis` router has a larger rule length, it matches `/api` requests first. 107 | > This is necessary as if the `ocular` router picked up requests, it would return 501 Unimplemented statuses (this is hardcoded). 108 | 109 | ## Should I use nginx in the docker compose.yaml file if I already have my own reverse proxy already set up? 110 | Yes, Ocular's nginx handles the _internal_ routing between the frontend and backend of Ocular. 111 | Your reverse proxy can be used to handle _external_ routing as usual. 112 | 113 | > As mentioned [here](https://github.com/simonwep/ocular-docker/discussions/11) 114 | 115 | ## When reverse proxying Ocular, which Docker service do I point to: frontend, backend, or nginx? 116 | Point your reverse proxy to Ocular's nginx, which in turn will handle routing between everything else. 117 | It may be helpful to use a `container_name` for Ocular's nginx to distinguish it from your own reverse proxy (e.g., `ocular-nginx`). 118 | If both your reverse proxy and Ocular are on the same docker network, you can use the container name `ocular-nginx` and its default internal port `80`, such as in the screenshot below (NPM): 119 | 120 | ![Screenshot 2025-02-16 101619](https://github.com/user-attachments/assets/f2ed13eb-0fab-4686-a74d-6f70a91a3cfb) 121 | 122 | > As mentioned [here](https://github.com/simonwep/ocular-docker/discussions/11) 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | Logo 5 |

6 | 7 |
8 | 9 |
10 |

Ocular on Docker

11 |

A ready-to-deploy docker compose setup for ocular

12 |
13 | 14 | 15 |
16 | Demo / 17 | Quickstart / 18 | Deploy / 19 | Main Repo 20 |
21 | 22 |
23 | 24 |

25 | The documentation has been moved here :) 26 |

27 | -------------------------------------------------------------------------------- /config/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | server { 9 | listen 80; 10 | server_name localhost; 11 | 12 | location /api/ { 13 | proxy_pass http://backend/; 14 | } 15 | 16 | location / { 17 | proxy_pass http://frontend; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | image: ghcr.io/simonwep/genesis:v1.4.1 4 | restart: unless-stopped 5 | volumes: 6 | - ./data:/app/.data 7 | command: start 8 | environment: 9 | - GENESIS_PORT 10 | - GENESIS_DB_PATH 11 | - GENESIS_CREATE_USERS 12 | - GENESIS_AUTHORIZED_URIS 13 | - GENESIS_JWT_SECRET 14 | - GENESIS_JWT_TOKEN_EXPIRATION 15 | - GENESIS_JWT_COOKIE_ALLOW_HTTP 16 | - GENESIS_USERNAME_PATTERN 17 | - GENESIS_KEY_PATTERN 18 | - GENESIS_DATA_MAX_SIZE 19 | - GENESIS_KEYS_PER_USER 20 | - GENESIS_GIN_MODE 21 | - GENESIS_LOG_MODE 22 | 23 | frontend: 24 | image: ghcr.io/simonwep/ocular:v1.12.0 25 | restart: unless-stopped 26 | environment: 27 | - OCULAR_DEMO 28 | 29 | nginx: 30 | image: nginx:1.26-alpine 31 | restart: unless-stopped 32 | ports: 33 | - "3030:80" 34 | volumes: 35 | - ./config/nginx.conf:/etc/nginx/nginx.conf 36 | depends_on: 37 | - backend 38 | - frontend 39 | -------------------------------------------------------------------------------- /gen-passwords.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | ADMIN_PASSWORD=$(openssl rand -hex 16) 5 | 6 | # Copy .env.example to .env if it doesn't exist 7 | if [ ! -f "$(dirname "$0")/.env" ]; then 8 | cp "$(dirname "$0")/.env.example" "$(dirname "$0")/.env" 9 | echo "Copied .env.example to .env" 10 | fi 11 | 12 | # Generate random secrets and admin user 13 | sed -i.bak \ 14 | -e "s#GENESIS_JWT_SECRET=.*#GENESIS_JWT_SECRET=$(openssl rand -hex 32)#g" \ 15 | -e "s#GENESIS_CREATE_USERS=admin!:.*#GENESIS_CREATE_USERS=admin!:$ADMIN_PASSWORD#g" \ 16 | "$(dirname "$0")/.env" 17 | 18 | echo "Created admin user with username 'admin' and password '$ADMIN_PASSWORD'" 19 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | # Function to check if a command exists 5 | command_exists() { 6 | $1 &>/dev/null 7 | } 8 | 9 | # Function to get the latest release version from a GitHub repository 10 | get_latest_release() { 11 | curl --silent "https://api.github.com/repos/$1/releases/latest" | 12 | grep '"tag_name":' | 13 | sed -E 's/.*"([^"]+)".*/\1/' 14 | } 15 | 16 | # Check if Docker is installed 17 | if ! command_exists docker; then 18 | echo "Docker is not installed. Please install Docker and try again." 19 | exit 1 20 | fi 21 | 22 | # Check if Docker Compose is installed 23 | if ! command_exists "docker compose"; then 24 | echo "Docker Compose is not available. Please install the latest version of Docker and try again." 25 | exit 1 26 | fi 27 | 28 | # Get the latest release version 29 | repository="simonwep/ocular-docker" 30 | latest_version=$(get_latest_release "$repository") 31 | latest_version_clean="${latest_version#v}" 32 | echo "The latest release version of $repository is $latest_version" 33 | 34 | # Check if the folder already exists and if it should be removed first 35 | if [ -d "ocular-docker-$latest_version_clean" ]; then 36 | echo "The folder ocular-docker-$latest_version_clean already exists." 37 | read -r -p " ? Do you want to remove it and continue? This will remove all data! (yes/no): " confirm 38 | if [ "$confirm" == "yes" ]; then 39 | echo " ✔ Removing existing folder..." 40 | rm -rf "ocular-docker-$latest_version_clean" 41 | else 42 | echo " ⅹ Aborting setup." 43 | exit 1 44 | fi 45 | fi 46 | 47 | # Download the latest release source code 48 | echo "Downloading the latest release source code..." 49 | curl -sL "https://github.com/$repository/archive/refs/tags/$latest_version.tar.gz" -o ocular-docker.tar.gz 50 | 51 | # Extract the release 52 | echo " → Extracting the release..." 53 | tar -xzf ocular-docker.tar.gz 54 | rm -rf ocular-docker.tar.gz 55 | cd "ocular-docker-$latest_version_clean" || { echo "Failed to enter the ocular-docker directory"; exit 1; } 56 | 57 | # Copy .env.example to .env 58 | echo "Setting up environment variables..." 59 | cp .env.example .env 60 | 61 | # Ask the user if the service should be available via HTTP 62 | read -r -p " ? Should the service be available via HTTP? (yes/no): " allow_http 63 | if [ "$allow_http" == "yes" ]; then 64 | sed -i.bak 's/GENESIS_JWT_COOKIE_ALLOW_HTTP=false/GENESIS_JWT_COOKIE_ALLOW_HTTP=true/' .env 65 | echo " ✔ The service will be available via HTTP." 66 | else 67 | echo " ⅹ The service will not be available via HTTP." 68 | fi 69 | 70 | # Generate secrets and an initial admin user 71 | echo "Generating secrets and an initial admin user..." 72 | ./gen-passwords.sh > /dev/null 73 | 74 | # Start Docker Compose services 75 | echo "Starting Docker Compose services..." 76 | docker compose up -d 77 | 78 | # Extract generated credentials 79 | GENESIS_CREATE_USERS=$(grep 'GENESIS_CREATE_USERS' .env | cut -d '=' -f2) 80 | ADMIN_USER=$(echo "$GENESIS_CREATE_USERS" | cut -d '!' -f1) 81 | ADMIN_PASSWORD=$(echo "$GENESIS_CREATE_USERS" | cut -d ':' -f2) 82 | 83 | # Texts for the info box 84 | texts=( 85 | "Setup complete!" 86 | "Ocular should be accessible under http://localhost:3030 in your browser :)" 87 | "" 88 | "You can log in as admin using the following credentials:" 89 | " → Username: $ADMIN_USER" 90 | " → Password: $ADMIN_PASSWORD" 91 | "" 92 | "What's next?" 93 | " → cd into the 'ocular-docker-$latest_version_clean' directory and run 'docker compose down' to stop the services." 94 | " → your data is located under the 'ocular-docker-$latest_version_clean/data' directory." 95 | ) 96 | 97 | # Print info box 98 | max_length=$(printf "%s\n" "${texts[@]}" | awk '{ if ( length > L ) { L=length } } END { print L }') 99 | box_width=$((max_length + 4)) 100 | 101 | printf '┌%*s┐\n' "$box_width" '' | tr ' ' '─' 102 | for text in "${texts[@]}"; do 103 | printf "│ %-${max_length}s │\n" "$text" 104 | done 105 | printf '└%*s┘\n' "$box_width" '' | tr ' ' '─' 106 | --------------------------------------------------------------------------------