diff --git a/README.md b/README.md
index 479f94cb..74ba5e80 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,90 @@
# selfh.st/icons
-This repository is a data store for the collection of icons found at [selfh.st/icons](https://selfh.st/icons). For more information, please see the project's [About](https://selfh.st/icons-about/) page.
+[selfh.st/icons](https://selfh.st/icons) is a collection of 4,400+ logos and icons for self-hosted (and non-self-hosted) software.
+
+The collection is available for browsing via the directory at [selfh.st/icons](https://selfh.st/icons) and served to users directly from this repo using the jsDelivr content delivery network.
+
+To self-host the collection, users can clone, download, or sync the repository with a tool like [git-sync](https://github.com/AkashRajpurohit/git-sync) and serve it with a web server of their choosing (Caddy, NGINX, etc.).
+
+
+### Color Options
+
+By default, most SVG icons are available in three color formats:
+
+* **Standard**: The standard colors of an icon without any modifications.
+* **Dark**: A modified version of an icon displayed entirely in black (```#000000```).
+* **Light**: A modified version of an icon displayed entirely in white (```#FFFFFF```).
+
+(Toggles to view icons by color type are available in the [directory hosted on the selfh.st website](https://selfh.st/icons).)
+
+### Custom Colors
+
+Because the dark and light versions of each icon are monochromatic, CSS can theoretically be leveraged to apply custom colors to the icons.
+
+This only works, however, when the SVG code is embedded directly onto a webpage. Unfortunately, most [integrations](https://selfh.st/apps/?tag=selfh-st-icons) link to the icons via an `` tag, which prevents styling from being overridden via CSS.
+
+As a workaround, a lightweight self-hosted server has been published via Docker that utilizes a URL parameter for color conversion on the fly. Continue reading for further instructions.
+
+
+#### Deploying the Custom Color Container
+
+* Introduction
+* Deploying the container
+* Configuring a reverse proxy (optional)
+* Linking to a custom icon
+* Changelog
+
+##### Introduction
+
+The Docker image below allows users to host a local server that acts as a proxy between requests and jsDelivr. When a color parameter is detected in the URL, the server will intercept the requests, fill the SVG file with that color, and serve it to the user.
+
+Once deployed, users can append ```?color=eeeeee``` to the end of a URL to specify a custom color (replacing ```eeeeee``` with any [hex color code](https://htmlcolorcodes.com/)).
+
+##### Deployment
+
+The container can be easily deployed via docker-compose with the following snippet:
+
+```
+selfhst-icons:
+ image: ghcr.io/selfhst/icons:latest
+ restart: unless-stopped
+ ports:
+ - 4050:4050
+```
+
+No volume mounts or environment variables are currently required.
+
+##### Reverse Proxy
+
+While out of the scope of this guide, many applications will require users to leverage HTTPS when linking to icons served from the container.
+
+The process to proxy the container and icons is straightforward. A sample Caddyfile configuration has been provided for reference:
+
+```
+icons.selfh.st {
+ reverse_proxy selfhst-icons:4050
+}
+```
+
+##### Linking
+
+After the container has been deployed, users can easily link to any existing icon within the collection:
+
+* ```https://icons.selfh.st/bookstack.svg```
+* ```https://icons.selfh.st/bookstack.png```
+* ```https://icons.selfh.st/bookstack-dark.webp```
+
+To customize the color, users **must** link to the *standard* version of an SVG icon that has available monochromatic (dark/light) versions. To do so, append a custom URL parameter referencing any [hex color code](https://htmlcolorcodes.com/):
+
+* ```https://icons.selfh.st/bookstack.svg?color=eeeeee```
+* ```https://icons.selfh.st/bookstack.svg?color=439b68```
+
+**Note the following:**
+
+* Only the standard icons accept URL parameters (for example, ```bookstack-light.svg?color=fff000``` will not yield a different color.
+* Only append the alpha-numeric portion of the hex color code to the URL. The server will append the ```#``` in the backend before passing it on for styling.
+
+##### Changelog
+
+* 2025-04-30: Initial release
\ No newline at end of file
diff --git a/build/dockerfile b/build/dockerfile
new file mode 100755
index 00000000..0e94eff5
--- /dev/null
+++ b/build/dockerfile
@@ -0,0 +1,11 @@
+FROM node:18-alpine
+WORKDIR /app
+
+COPY package.json ./
+RUN npm install
+
+COPY server.js .
+
+EXPOSE 4050
+
+CMD ["node", "server.js"]
\ No newline at end of file
diff --git a/build/package.json b/build/package.json
new file mode 100755
index 00000000..6ddc0425
--- /dev/null
+++ b/build/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "test-repo",
+ "version": "1.0.0",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js"
+ },
+ "type": "module",
+ "dependencies": {
+ "express": "^4.18.2",
+ "node-fetch": "^3.3.0"
+ }
+}
\ No newline at end of file
diff --git a/build/server.js b/build/server.js
new file mode 100755
index 00000000..5c812048
--- /dev/null
+++ b/build/server.js
@@ -0,0 +1,89 @@
+import express from 'express'
+import fetch from 'node-fetch'
+import path from 'path'
+
+const app = express()
+const PORT = 4050
+const CDN_ROOT = 'https://cdn.jsdelivr.net/gh/selfhst/icons'
+const CDN_PATH = 'svg'
+
+async function fileExists(url) {
+ try {
+ const resp = await fetch(url, { method: 'HEAD' });
+ return resp.ok;
+ } catch {
+ return false;
+ }
+}
+
+async function fetchAndPipe(url, res) {
+ const response = await fetch(url);
+ if (!response.ok) return res.status(404).send('File not found');
+ res.type(path.extname(url).slice(1));
+ response.body.pipe(res);
+}
+
+app.get('/*', async (req, res) => {
+ const urlPath = req.path;
+ const extMatch = urlPath.match(/\.(\w+)$/);
+ if (!extMatch)
+ return res.status(404).send('File extension missing');
+
+ const ext = extMatch[1].toLowerCase();
+ if (!['png', 'webp', 'svg'].includes(ext))
+ return res.status(404).send('Format not supported');
+
+ const filename = urlPath.slice(1);
+ const lowerFilename = filename.toLowerCase();
+
+ const isSuffix = lowerFilename.endsWith('-light.svg') || lowerFilename.endsWith('-dark.svg');
+
+ if (isSuffix) {
+ return fetchAndPipe(`${CDN_ROOT}/${CDN_PATH}/${filename}`, res);
+ }
+
+ let mainUrl;
+ if (ext === 'png') {
+ mainUrl = `${CDN_ROOT}/png/${filename}`;
+ } else if (ext === 'webp') {
+ mainUrl = `${CDN_ROOT}/webp/${filename}`;
+ } else if (ext === 'svg') {
+ mainUrl = `${CDN_ROOT}/svg/${filename}`;
+ } else {
+ mainUrl = null;
+ }
+
+ const hasColor = !!req.query['color'] && req.query['color'].trim() !== '';
+
+ if (ext === 'svg') {
+ if (hasColor) {
+ const baseName = filename.replace(/\.(png|webp|svg)$/, '');
+ const suffixUrl = `${CDN_ROOT}/${CDN_PATH}/${baseName}-light.svg`;
+ if (await fileExists(suffixUrl)) {
+ let svgContent = await fetch(suffixUrl).then(r => r.text());
+ const color = req.query['color'].startsWith('#') ? req.query['color'] : `#${req.query['color']}`;
+ svgContent = svgContent
+ .replace(/style="[^"]*fill:\s*#fff[^"]*"/gi, (match) => {
+ console.log('Replacing style fill:', match);
+ return match.replace(/fill:\s*#fff/gi, `fill:${color}`);
+ })
+ .replace(/fill="#fff"/gi, `fill="${color}"`);
+ return res.type('image/svg+xml').send(svgContent);
+ } else {
+ return fetchAndPipe(mainUrl, res);
+ }
+ } else {
+ return fetchAndPipe(mainUrl, res);
+ }
+ } else {
+ // PNG/WebP: serve directly
+ return fetchAndPipe(mainUrl, res);
+ }
+});
+
+app.get('/', (req, res) => {
+ res.send('Self-hosted icon server');
+});
+app.listen(PORT, () => {
+ console.log(`Listening on port ${PORT}`);
+});
\ No newline at end of file
diff --git a/png/deployarr-dark.png b/png/deployrr-dark.png
similarity index 100%
rename from png/deployarr-dark.png
rename to png/deployrr-dark.png
diff --git a/png/deployarr-light.png b/png/deployrr-light.png
similarity index 100%
rename from png/deployarr-light.png
rename to png/deployrr-light.png
diff --git a/png/deployarr.png b/png/deployrr.png
similarity index 100%
rename from png/deployarr.png
rename to png/deployrr.png
diff --git a/png/positive-intentions-dark.png b/png/positive-intentions-dark.png
index 5cf8c77e..20cded79 100755
Binary files a/png/positive-intentions-dark.png and b/png/positive-intentions-dark.png differ
diff --git a/png/positive-intentions-light.png b/png/positive-intentions-light.png
index efc83ca5..3368d588 100755
Binary files a/png/positive-intentions-light.png and b/png/positive-intentions-light.png differ
diff --git a/svg/deployarr-dark.svg b/svg/deployrr-dark.svg
similarity index 100%
rename from svg/deployarr-dark.svg
rename to svg/deployrr-dark.svg
diff --git a/svg/deployarr-light.svg b/svg/deployrr-light.svg
similarity index 100%
rename from svg/deployarr-light.svg
rename to svg/deployrr-light.svg
diff --git a/svg/deployarr.svg b/svg/deployrr.svg
similarity index 100%
rename from svg/deployarr.svg
rename to svg/deployrr.svg
diff --git a/svg/positive-intentions-dark.svg b/svg/positive-intentions-dark.svg
index 978db1e6..ae57b2d6 100755
--- a/svg/positive-intentions-dark.svg
+++ b/svg/positive-intentions-dark.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/svg/positive-intentions-light.svg b/svg/positive-intentions-light.svg
index 1f1b8945..03a99db4 100755
--- a/svg/positive-intentions-light.svg
+++ b/svg/positive-intentions-light.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/webp/deployarr-dark.webp b/webp/deployrr-dark.webp
similarity index 100%
rename from webp/deployarr-dark.webp
rename to webp/deployrr-dark.webp
diff --git a/webp/deployarr-light.webp b/webp/deployrr-light.webp
similarity index 100%
rename from webp/deployarr-light.webp
rename to webp/deployrr-light.webp
diff --git a/webp/deployarr.webp b/webp/deployrr.webp
similarity index 100%
rename from webp/deployarr.webp
rename to webp/deployrr.webp
diff --git a/webp/positive-intentions-dark.webp b/webp/positive-intentions-dark.webp
index 0c960516..eb5201f2 100755
Binary files a/webp/positive-intentions-dark.webp and b/webp/positive-intentions-dark.webp differ
diff --git a/webp/positive-intentions-light.webp b/webp/positive-intentions-light.webp
index 3169fc97..082e1e27 100755
Binary files a/webp/positive-intentions-light.webp and b/webp/positive-intentions-light.webp differ