{"id":19866096,"url":"https://github.com/vas3k/pepic","last_synced_at":"2026-01-14T17:14:33.234Z","repository":{"id":40381883,"uuid":"274757414","full_name":"vas3k/pepic","owner":"vas3k","description":"Image and video proxy for my pet-projects","archived":false,"fork":false,"pushed_at":"2024-05-20T06:50:24.000Z","size":802,"stargazers_count":92,"open_issues_count":1,"forks_count":15,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-01-13T01:09:50.933Z","etag":null,"topics":["golang","image-processing","image-storage","media-proxy","pet-project","proxy","upload","video-processing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vas3k.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-24T19:55:00.000Z","updated_at":"2025-12-25T06:38:39.000Z","dependencies_parsed_at":"2024-05-20T07:45:24.194Z","dependency_job_id":null,"html_url":"https://github.com/vas3k/pepic","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vas3k/pepic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vas3k%2Fpepic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vas3k%2Fpepic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vas3k%2Fpepic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vas3k%2Fpepic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vas3k","download_url":"https://codeload.github.com/vas3k/pepic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vas3k%2Fpepic/sbom","scorecard":{"id":916649,"data":{"date":"2025-08-11","repo":{"name":"github.com/vas3k/pepic","commit":"559ba26db60d8433fb0b055e2aef28be6f52bc35"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/deploy.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":3,"reason":"Found 6/19 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/deploy.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/vas3k/pepic/deploy.yml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:1","Warn: containerImage not pinned by hash: Dockerfile:12: pin your Docker image by updating alpine:latest to alpine:latest@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 18 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":4,"reason":"6 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2025-3553 / GHSA-mh63-6h87-95cp","Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T21:15:42.869Z","repository_id":40381883,"created_at":"2025-08-24T21:15:42.869Z","updated_at":"2025-08-24T21:15:42.869Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28427207,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T16:38:47.836Z","status":"ssl_error","status_checked_at":"2026-01-14T16:34:59.695Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","image-processing","image-storage","media-proxy","pet-project","proxy","upload","video-processing"],"created_at":"2024-11-12T15:25:00.176Z","updated_at":"2026-01-14T17:14:33.216Z","avatar_url":"https://github.com/vas3k.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"static/images/logo.png\" width=\"256\" alt=\"\"\u003e\n  \u003ch1\u003ePEPIC\u003c/h1\u003e\n\u003c/div\u003e\n\nPepic is a small self-hosted media proxy that helps me to upload, store, serve and convert pictures and videos on my own servers.\n\nCurrently, I use it as a main storage for media files in my [pet-projects](https://github.com/vas3k/vas3k.club) and on [my blog](https://vas3k.blog).\n\nPepic can upload and optimize media files in-flight to save you money and bandwidth. It's highly recommended to set ip up in combination with Cloudflare CDN for better caching.\n\nInternally it uses [ffmpeg](https://ffmpeg.org/download.html) for videos and [vips](https://libvips.github.io/libvips/install.html) for images, which makes it quite fast and supports many media file formats.\n\nImages: **JPG, PNG, GIF, WEBP, SVG, HEIF, TIFF, AVIF, etc**\n\nVideo: **basically everything ffmpeg supports**\n\nPepic is open source, however it's not meant to be used by anyone. Only if you're brave (like me). Scroll down this README for better alternatives.\n\n\n## Features\n\n- **Local file storage**: Upload files as multipart/form-data or as a simple byte stream and store them to a local directory.\n- **Automatic GIF to video conversion**: Because GIFs suck, they slow down web pages and don't support hardware acceleration.\n- **Image and video transcoding and quality optimization**: Transcode and optimize media files on upload or on-the-fly. If you are doing a public storage, you can pre-set your own quality settings to save disk space and bandwidth.\n- **Dynamic resizing**: Easily resize images just by modifying original URL. You can automatically generate image/video previews on demand without uploading multiple versions of the file.\n- **High performance**: Pepic uses native libraries like `ffmpeg` and `vips` for video and image processing to ensure high performance and fast processing times.\n- **Local and containerized environments**: Designed to run smoothly in both local environments and within Docker containers, making it versatile for development and deployment.\n- **Custom configuration**: Flexible configuration options through [config.yml](etc/pepic/config.yml), allowing adjustments to image size, quality, automatic conversion, templates, etc.\n\n![](static/images/screenshot1.png)\n\n## 🤖 How to Run\n\n1. Install `vips` and `ffmpeg` first, as they are two main external dependencies\n\n```bash\nbrew install vips ffmpeg\n```\n\n2. Clone this repo\n\n```bash\ngit clone git@github.com:vas3k/pepic.git\ncd pepic\n```\n\n3. Run the following command to build and start the app\n\n```bash\ngo run main.go serve --config ./etc/pepic/config.yml\n```\n\n\u003e ⚠️ If you're getting `invalid flag in pkg-config` error, run `brew install pkg-config` and `export CGO_CFLAGS_ALLOW=\"-Xpreprocessor\"`. Then try `go run` again.\n\n3. Go to [localhost:8118](http://localhost:8118) and enjoy!\n\n## 🐳 Using Docker Compose\n\nYou can find [docker-compose.example.yml](./docker-compose.example.yml) in this repo and adapt it to your own needs. \n\n1. Get [Docker](https://www.docker.com/get-started) and [Docker Compose](https://www.digitalocean.com/community/tutorial-collections/how-to-install-docker-compose)\n\n2. Download the Docker Compose example file and save it as `docker-compose.yml` on your local machine\n\n```bash\ncurl https://raw.githubusercontent.com/vas3k/pepic/master/docker-compose.example.yml -o docker-compose.yml\n```\n\n3. Now run it\n\n```bash\ndocker-compose up\n```\n\n4. Go to [http://localhost:8118](http://localhost:8118) and try uploading something. You should see uploaded images or videos in the local directory (`./uploads`) after that.\n\n\n## 🧶 Usage\n\n### Configuration options\n\n```yaml\nglobal:\n  host: 0.0.0.0\n  port: 8118\n  base_url: \"http://0.0.0.0:8118/\"  # trailing slash is important\n  secret_code: \"\"                   # secret word to protect you from strangers (don't use your password here, it's stored as plain text)\n  max_upload_size: \"500M\"           # number + K, M, G, T or P\n  file_tree_split_chars: 3          # abcde.jpg -\u003e ab/cd/e.jpg (never change this after release!)\n\nstorage:\n  type: fs        # only \"fs\" (file system) is supported for now, but you can code your own storage class\n  dir: uploads/   # relative or absolute path for actual files (back it up!)\n\nimages:\n  store_originals: false    # use \"true\" if you want byte-by-byte match of uploaded files (useful for photo blogs)\n  original_length: 1900     # long side length in px to auto-resize originals (only if store_originals=false)\n  auto_convert: false       # mime type to auto-convert uploaded images (\"image/jpeg\", \"image/png\" or false)\n  live_resize: true         # enables special URLs that return resized images (increases storage usage)\n  jpeg_quality: 95          # default quality for any saved jpegs\n  png_compression: 0        # 0 - default, -1 - no compression, -2 - best speed, -3 - best compression (yes, with minus)\n  gif_convert: \"video/mp4\"  # video format for auto-converting gifs (ignored on store_originals=true)\n\nvideos:\n  store_originals: false    # use \"true\" if you want to store original files (browser compatibility is on you)\n  original_length: 720      # resize uploaded videos (only if store_originals=false)\n  live_resize: false        # turned off by default to save disk space and your cpu (always returns original)\n  auto_convert: \"video/mp4\" # mime type to auto-convert uploaded images (for example \"video/mp4\")\n  ffmpeg:\n    temp_dir: \"/tmp\"        # temp directory for video transcoding\n    preset: \"slow\"          # ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo\n    crf: 24                 # quality factor — 0-51, where 0 is lossless, 51 — pixelated shit. 23-28 recommended.\n    buffer_size: 1024000    # other standard ffmpeg params, you can google them\n    video_codec: \"libx264\"\n    video_bitrate: \"1024k\"\n    video_profile: \"main\"\n    audio_codec: \"aac\"\n    audio_bitrate: \"128k\"\n    mov_flags: \"+faststart\"\n    pix_fmt: \"yuv420p\"\n\nmeta:  # optional, only if you use web interface\n  image_templates:  # add your custom templates here for easier copy-paste\n    - title: \"URL\"\n      template: \"{{ file.Url }}\"\n    - title: \"Simple Markdown\"\n      template: \"![]({{ file.Url }})\"\n  video_templates:\n    - title: \"URL\"\n      template: \"{{ file.Url }}\"\n    - title: \"Simple Markdown\"\n      template: \"![]({{ file.Url }})\"\n  multi_templates:\n    - title: \"2 in a row\"\n      template: \"{% for file in files %}![]({{ file.Url }}) {% endfor %}\"\n\n```\n\n### Resizing images on demand\n\nIf your image URL looks like this: **`https://imgs.com/file.jpg`**\n\nAdd /500/ to its URL to get 500px (on the long side) version: **`https://imgs.com/500/file.jpg`**\n\nWorks only if `live_resize` option is set to `true`. If `live_resize=false` — it returns the original version. Same for video transcoding (where it's off by default).\n\n\u003e ⚠️ **Note:** Each resized version is saved as a separate file (with the same hash). When the same version of the file is requested again, Pepic just read it from disk and does not waste CPU time resizing it again. However, if you have many resized versions stored, this can eat quite a bit of disk space. Be careful.\n\n\n### Converting file formats on demand\n\n// Not implemented yet, sorry... PRs are welcome\n\n\n### GIF to video conversion\n\nBecause GIFs are terrible, Pepic automatically converts them to mp4 videos by default. You can change it to any other format you like using `gif_convert` setting in `config.yml`.\n\nYou can disable this behavior only if you set the `store_originals=true` flag, then GIF files will be saved \"as is\".\n\n![](static/images/screenshot2.png)\n\n\n## 🚢 Production Deployment\n\n\u003e ⚠️ If you plan to host anything bigger than a blog, always put Pepic behind a CDN. CloudFlare offers a free one if you don't hate big corporations :D\n\nLet's say, you want to host it on `https://media.mydomain.org`\n\n1. Modify `etc/pepic/config.yml` to your taste\n\n```yaml\nglobal:\n  host: 0.0.0.0 \n  port: 8118  # internal host and port, leave it as it is\n  base_url: \"https://media.mydomain.org\"\n  secret_code: \"secretpass\"\n  max_upload_size: \"500M\"\n```\n\n2. Build and run production version of the Docker container\n\nDon't forget to mount upload volume to store files on host (or you can lose those files when the container is killed).\n\n```bash\ndocker run -p 8118:8118 -v /host/dir/uploads:/app/uploads --restart=unless-stopped $(docker build -q .)\n```\n\nIf you prefer docker-compose, you can use it too. Check out the included [docker-compose.example.yml](docker-compose.example.yml). \nYou can easily transform it into your favourite k8s config or whatever is fashionable this summer. \n\n\u003e 👍 Don't forget to periodically backup the `/host/dir/uploads` directory just in case :)\n\n3. Use nginx, traefik, k8s or your other favourite proxy\n\nJust proxy all calls from the domain (media.mydomain.org) to Pepic backend (0.0.0.0:8118). Don't forget to set `max file size` and `proxy timeot` directives to avoid gateway errors on big files (especially videos).\n\nHere's an example for nginx:\n\n```nginx\nserver {\n    listen 80;\n    server_name media.mydomain.org;\n\n    client_max_body_size 500M;\n    real_ip_header X-Real-IP;\n\n    location / {\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\n        proxy_read_timeout 300;\n        proxy_connect_timeout 300;\n        proxy_send_timeout 300;\n        send_timeout 300;\n\n        proxy_set_header Host $http_host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_redirect off;\n        proxy_buffering off;\n\n        proxy_pass http://0.0.0.0:8118;\n    }\n}\n```\n\n## 😍 Contributions\n\nContributions are welcome.  \n\nOpen an [Issue](https://github.com/vas3k/vas3k.club/issues) if you want to report a bug or propose an idea.\n\n## ✅ TODO\n\n- [ ] Tests :D\n- [ ] Upload by URL\n- [ ] Crop, rotate and other useful transformations (face blur? pre-loader generator?)\n- [ ] Live conversion by changing file's extension \n- [ ] Set format and media quality during the upload (using GET/POST params?)\n\n\n## 🤔 Alternatives\n\nAfter reading all this, you probably realized how bad it is and looking for other alternatives. Here's my recommendations:\n\n- [imgproxy](https://github.com/imgproxy/imgproxy)\n- [imaginary](https://github.com/h2non/imaginary)\n- [flyimg](https://github.com/flyimg/flyimg)\n\n\n## 👩‍💼 License \n\nIt's [MIT](LICENSE).\n\nContact me if you have any questions — [me@vas3k.ru](mailto:me@vas3k.ru).\n\n❤️\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvas3k%2Fpepic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvas3k%2Fpepic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvas3k%2Fpepic/lists"}