{"id":15366912,"url":"https://github.com/cupcakearmy/morphus","last_synced_at":"2025-04-15T12:33:31.086Z","repository":{"id":61644506,"uuid":"428608005","full_name":"cupcakearmy/morphus","owner":"cupcakearmy","description":"a lightweight image resizing proxy","archived":false,"fork":false,"pushed_at":"2022-10-18T11:59:19.000Z","size":565,"stargazers_count":10,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-28T20:12:13.351Z","etag":null,"topics":["image-cdn","image-processing","image-proxy"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cupcakearmy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-11-16T10:20:07.000Z","updated_at":"2024-08-26T17:24:11.000Z","dependencies_parsed_at":"2022-10-20T09:45:39.579Z","dependency_job_id":null,"html_url":"https://github.com/cupcakearmy/morphus","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fmorphus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fmorphus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fmorphus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cupcakearmy%2Fmorphus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cupcakearmy","download_url":"https://codeload.github.com/cupcakearmy/morphus/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249072314,"owners_count":21208168,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["image-cdn","image-processing","image-proxy"],"created_at":"2024-10-01T13:20:15.736Z","updated_at":"2025-04-15T12:33:31.066Z","avatar_url":"https://github.com/cupcakearmy.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# morphus\n\n\u003cp align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"./design/round.png\" width=150 /\u003e\n  \u003cbr\u003e\u003cbr\u003e\n\u003c/p\u003e\n\n\u003ca href=\"https://discord.gg/nuby6RnxZt\"\u003e\n  \u003cimg alt=\"discord\" src=\"https://img.shields.io/discord/252403122348097536?style=for-the-badge\" /\u003e\n  \u003cimg alt=\"docker pulls\" src=\"https://img.shields.io/docker/pulls/cupcakearmy/morphus?style=for-the-badge\" /\u003e\n  \u003cimg alt=\"Docker image size badge\" src=\"https://img.shields.io/docker/image-size/cupcakearmy/morphus?style=for-the-badge\" /\u003e\n  \u003cimg alt=\"Latest version\" src=\"https://img.shields.io/github/v/release/cupcakearmy/moprhus?style=for-the-badge\" /\u003e\n\u003c/a\u003e\n\nA lightweight image resizing and effect proxy that caches image transformations.\nThe heavy lifting is done by [`libvips`](https://github.com/libvips/libvips) and [`sharp`](https://github.com/lovell/sharp)\n\n\u003e **⚠️ Currently under development**\n\n## 🌈 Features\n\n- Config driven\n- Domain protection\n- Host verification\n- Multiple storage adapters (Local, Minio, S3, GCP)\n- Auto format based on `Accept` header\n- ETag caching\n- Presets and optional forcing of presets\n\n## 🏗 Installation\n\nThe easies way to run is using docker.\n\n```yaml\n# morphus.yaml\nallowedDomains:\n  - !regexp ^https?:\\/\\/images.unsplash.com\n```\n\n```yaml\n# docker-compose.yaml\nversion: '3.8'\n\nservices:\n  app:\n    image: cupcakearmy/morphus\n    ports:\n      - '80:80'\n```\n\n```bash\ndocker-compose up\n```\n\n\u003e For more realistic `docker-compose` files check the `docker` directory.\n\n## 🎪 Examples\n\n**Simple resize**: `?width=2000\u0026resize=contain`\n\n```\nhttps://my-morphus.org/api/image?width=2000\u0026resize=contain\u0026url=https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b\n```\n\n**Chose a format**: `?format=webp`\n\n```\nhttps://my-morphus.org/api/image?format=webp\u0026width=2000\u0026resize=contain\u0026url=https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b\n```\n\n**Chose a format with a given quality**: `?format=webp|quality:90`\n\n```\nhttps://my-morphus.org/api/image?format=webp|quality:90\u0026width=2000\u0026resize=contain\u0026url=https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b\n```\n\n**With some transformation operations**: `?op=rotate|angle:90\u0026op=sharpen|sigma:1,flat:2`\n\nThis is transforming the image once by `rotate` with the argument `angle: 90` and `sharpen` with the arguments of `sigma: 1` and `flat: 2`.\n\n```\nhttps://my-morphus.org/api/image?width=2000\u0026resize=contain\u0026op=rotate|angle:90\u0026op=sharpen|sigma:1,flat:2\u0026url=https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b\n```\n\n**With custom presets**: `?preset=thumbnail`\n\n```yaml\n# morphus.yaml\npresets:\n  thumbnail: ?width=300\u0026height=150\u0026resize=contain\n```\n\n```\nhttps://my-morphus.org/api/image?preset=thumbnail\u0026url=https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b\n```\n\n## 💻 Usage\n\n| Parameter | Syntax                                                             | Example                                                    |\n| --------- | ------------------------------------------------------------------ | ---------------------------------------------------------- |\n| url       | URL                                                                | `?url=https://cdn.example.org/dog-full-res.png`            |\n| format    | ComplexParameter                                                   | `?format=webp` `?format=webp\\|quality:90,progressive:true` |\n| resize    | [sharp.fit](https://sharp.pixelplumbing.com/api-resize#parameters) | `?resize=contain`                                          |\n| width     | number                                                             | `?width=500`                                               |\n| height    | number                                                             | `?width=500`                                               |\n| op        | ComplexParameter[]                                                 | `?op=rotate\\|angle:90\u0026op=sharpen\\|sigma:1,flat:2`          |\n\n### ComplexParameter\n\nThe syntax for the `ComplexParameter` is as follows:\n\n###### Without options\n\n```\n?param=\u003cname\u003e\n```\n\n###### With two options\n\n```\n?param=\u003cname\u003e|optionA:1,optionB:true\n```\n\n## ⚙️ Configuration\n\nConfig files are searched in the current working directory under `morphus.yaml`.\n\nConfiguration can be done either thorough config files or env variables. The usage of a config file is recommended. Below is a table of available configuration options, for more details see below.\n\n| Config             | Environment                                                                      | Default   | Description                                                                                                                                                |\n| ------------------ | -------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `port`             | `PORT`                                                                           | 80        | The port to bind.                                                                                                                                          |\n| `address`          | `ADDRESS`                                                                        | 127.0.0.1 | The address to bind.                                                                                                                                       |\n| `logLevel`         | `LOG_LEVEL`                                                                      | info      | The [log level](https://getpino.io/#/docs/api?id=loggerlevel-string-gettersetter) to use. Possible values: trace, debug, info, warn, error, fatal, silent. |\n| `presets`          | not available                                                                    | null      | Predefined presets. See below for an example.                                                                                                              |\n| `onlyAllowPresets` | `ONLY_ALLOW_PRESETS`                                                             | false     | Whether to allow only presets. This can prevent unfair usage.                                                                                              |\n| `allowedDomains`   | [unsupported for now as ENV](https://github.com/mozilla/node-convict/issues/399) | null      | The domains that are allowed to be used as image sources.                                                                                                  |\n| `allowedHosts`     | [unsupported for now as ENV](https://github.com/mozilla/node-convict/issues/399) | null      | The hosts that are allowed to access the images.                                                                                                           |\n| `cleanUrls`        | `CLEAN_URL`                                                                      | Fragment  | Whether source URLs are cleaned.                                                                                                                           |\n| `maxAge`           | `MAX_AGE`                                                                        | 1d        | How long the served images are marked as cached, after that ETag is used to revalidate.                                                                    |\n| `storage`          | `STORAGE`                                                                        | `local`   | The storage driver to use. Possible values: `local`, `minio`, `s3`, `gcs`.                                                                                 |\n\n### Storage Drivers\n\n#### Local\n\n| Config         | Environment    | Default  | Description                   |\n| -------------- | -------------- | -------- | ----------------------------- |\n| `local.assets` | `LOCAL_ASSETS` | ./assets | The path to the assets folder |\n\n#### Minio\n\n| Config            | Environment        | Default | Description                 |\n| ----------------- | ------------------ | ------- | --------------------------- |\n| `minio.accessKey` | `MINIO_ACCESS_KEY` |         | The access key for Minio    |\n| `minio.secretKey` | `MINIO_SECRET_KEY` |         | The secret key for Minio    |\n| `minio.endpoint`  | `MINIO_ENDPOINT`   |         | The endpoint for Minio      |\n| `minio.bucket`    | `MINIO_BUCKET`     |         | The bucket to use for Minio |\n| `minio.region`    | `MINIO_REGION`     |         | The region for Minio        |\n\n###### Example\n\n```yaml\n# morphus.yaml\n\nstorage: minio\nminio:\n  accessKey: minioadmin\n  secretKey: minioadmin\n  bucket: morphus\n  endpoint: http://localhost:9000\n```\n\n#### AWS S3\n\n| Config         | Environment            | Default | Description                     |\n| -------------- | ---------------------- | ------- | ------------------------------- |\n| `s3.bucket`    | `S3_BUCKET`            |         | The S3 bucket to use            |\n| `s3.region`    | `S3_REGION`            |         | The S3 region to use            |\n| `s3.accessKey` | `S3_ACCESS_KEY_ID`     |         | The S3 access key id to use     |\n| `s3.secretKey` | `S3_SECRET_ACCESS_KEY` |         | The S3 secret access key to use |\n\n###### Example\n\n```yaml\n# morphus.yaml\n\nstorage: s3\n\ns3:\n  accessKey: abc\n  secretKey: def\n  bucket: morphus\n```\n\n#### Google Cloud Storage\n\n| Config            | Environment        | Default | Description             |\n| ----------------- | ------------------ | ------- | ----------------------- |\n| `gcs.bucket`      | `GCS_BUCKET`       |         | The GCS bucket to use   |\n| `gcs.keyFilename` | `GCS_KEY_FILENAME` |         | The GCS key file to use |\n\n\u003e Due to Google Cloud requiring a keyfile, that keyfile needs to be available to morphus. In docker this means passing it into the volume for example.\n\n###### Example\n\n```yaml\n# morphus.yaml\n\nstorage: gcs\ngcs:\n  bucket: morphus\n  keyFilename: keyfile.json\n```\n\n### Presets\n\nWith the help of presets you can give predefined sets of operations and transformations.\nClients then can use the presets without specifying the exact parameters.\n\nThe syntax is an object that maps a preset name to a value. The value is a valid url query.\n\n```yaml\npresets:\n  sm: ?format=webp|quality:90\u0026width=500\u0026resize=contain\n  md: ?format=webp|quality:90\u0026width=1000\u0026resize=contain\n  lg: ?format=webp|quality:90\u0026width=2000\u0026resize=contain\n```\n\nA client can the request an image with the following url\n\n```\nhttps://my-morphus.org/api/image?preset=sm\u0026url=https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b\n```\n\n### Only allow presets\n\nThis feature can help reduce abuse of the server by only allowing.\nWhen `onlyAllowPresets` is set no other parameter is allowed besides `url` and `preset`.\nIf possible it's recommended to turn this on.\n\n```yaml\nonlyAllowPresets: true\n```\n\n### Allowed Domains\n\nAllowed domains are a way to secure the service by only allowing certain remote domains as possible sources of images.\n\nYou can provide a `string` which will match as prefix or `RegExp` that allow for more powerful control.\n\nIf omitted every domain is allowed.\n\n```yaml\nallowedDomains:\n  # This will match any URL that starts with the string.\n  - https://my.cloud.org\n\n  # For regexp you need to add the !regexp tag in from of it.\n  - !regexp ^https?:\\/\\/images.unsplash.com\n```\n\n### Allowed Hosts\n\nSame syntax as for allowed domains.\n\nAllowed hosts enables you to whitelist a number of `origins`.\n\nIf ommtted any origin is allowed.\n\n```yaml\nallowedHosts:\n  - https://my.cloud.org\n```\n\n###### Note\n\nWhen using the url in an `\u003cimg\u003e` tag you need to add the `\u003cimg crossorigin=\"anonymous\"\u003e` attribute to enable sending the `origin` header to the server. Read more [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-crossorigin)\n\n### Clean URLs\n\nThis option allows cleaning the source URLs to remove duplicates. allowed options are `off`, `fragment`, `query`.\n\n###### Example\n\n| Type       | URL                                                                             |\n| ---------- | ------------------------------------------------------------------------------- |\n| Original   | `https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b?lang=en#chapter1` |\n| `off`      | `https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b?lang=en#chapter1` |\n| `fragment` | `https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b?lang=en`          |\n| `query`    | `https://images.unsplash.com/photo-1636839270984-1f7cbc2b4c4b`                  |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcupcakearmy%2Fmorphus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcupcakearmy%2Fmorphus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcupcakearmy%2Fmorphus/lists"}