Development of Snippy Open-source Code Editor by Kenneth LambDevelopment of Snippy Open-source Code Editor by Kenneth Lamb

Development of Snippy Open-source Code Editor

Kenneth Lamb

Kenneth Lamb

Snippy

Snippy is an open-source CodePen-style app for writing, previewing, and sharing HTML, CSS, and JavaScript snippets. Anyone can run it locally for testing or deploy it in production by pulling pre-built images from Docker Hub.
License: MIT
Docker Hub images: kennyl777/snippy-api, kennyl777/snippy-frontend, kennyl777/snippy-db, kennyl777/snippy-minio

Table of contents

Overview

Snippy is a multi-service Docker Compose app:
Service Role Source db MySQL 8 database (schema bootstrap via init.sh) snippy/db api Node/TypeScript Express API snippy/backend frontend Angular SPA snippy/frontend minio / minio-init Optional object storage for asset uploads Chainguard MinIO + snippy/minio
Compose loads environment variables from a root env file and wires the services together.
Two supported workflows:
Local development / testing Production deploy Compose file docker-compose.yml docker-compose.prod.example.yml Images Built locally from Dockerfile.dev Pulled from Docker Hub (kennyl777/snippy-*) Frontend ng serve on port 4200 (hot reload) nginx on container port 80 Env file .env stack.env (or Portainer stack env) CI / image publish .github/workflows/ci.yml on PRs .github/workflows/docker-image.yml Network Default Compose network External Docker network named NPM (for Nginx Proxy Manager)

Do not use the root docker-compose.yml for production. It bind-mounts source, runs ng serve, and is intended only for local testing.

Features

Shipped today:
Live HTML / CSS / JS editor with instant preview, split layouts, extra themes, and optional Vim keymap
Per-account editor preferences (theme, keymap, font, indent, line numbers, wrapping, folding, autocomplete) with Settings live preview
Guest /try editor, starter templates on /snippet, and public /embed/:shortId player (tabs, editable, theme query params)
Anonymous Explore (/public), tag browse (/tags/:tag), profiles, and collection pages without logging in
Public and private snippets, shareable links, full-page view
Account privacy toggle (private profiles hidden from others)
Favorites, threaded comments (@username links), follows, collections, and forking (forks list + parent attribution)
Local draft autosave in localStorage (including /try after login)
External CSS / JS resources on snippets
ZIP export and import of snippet files
Asset uploads, insert-at-cursor from the editor, usage counts, profile pictures, and list snapshots when MinIO is enabled
Secret share links for private pens (/s/:token)
Command palette (Ctrl/Cmd+K)
Coming soon (UI may show placeholders):
Projects

Prerequisites

Docker with Compose v2 (docker compose)
Node.js 22+ and npm 10+ (for root scripts: format, generate OpenAPI, check:all)
A terminal and basic familiarity with Docker
An Auth0 free account (required for login)
For production behind a domain: something that terminates TLS (this repo documents Nginx Proxy Manager + Portainer, which is optional but recommended)
Verify Docker:

Auth0 and HTTPS

Auth0 Single Page Applications expect a secure origin in real browsers for many flows. Practical options:
Local testing on the same machine — http://localhost:4200 is generally accepted by Auth0 for development.
Accessing a Docker host by LAN IP — Auth0 will often fail on plain http://192.168.x.x. Put HTTPS in front (self-signed or real cert via Nginx Proxy Manager) and use that hostname in Auth0.
Production — use a real domain with HTTPS and register those URLs in Auth0.

Auth0 setup

Complete this before starting either local or production stacks.

1. Create a Single Page Application

Sign up / log in at Auth0.
Applications → Applications → Create Application.
Choose Single Page Web Applications.
Open the app Settings and copy:
Domain → AUTH0_DOMAIN
Client ID → AUTH0_CLIENT_ID

2. Application URIs

Under Application URIs, set values for every environment you will use.
Local development example:
Field Value Allowed Callback URLs http://localhost:4200/home Allowed Logout URLs http://localhost:4200/ Allowed Web Origins http://localhost:4200
Production example (replace with your domain):
Field Value Allowed Callback URLs https://snippy.example.com/home Allowed Logout URLs https://snippy.example.com/ Allowed Web Origins https://snippy.example.com
You can list multiple origins comma-separated if you run both local and production against the same Auth0 app.
Save the application.

3. Create an API

Applications → APIs → Create API.
Name can be anything (for example Snippy API).
Identifier should match what you put in AUTH0_AUDIENCE. Common default used by this project: http://localhost:3000
Leave signing algorithm as RS256 and save.
Use that same identifier as AUTH0_AUDIENCE in your env file (local and production). The API Identifier is a string identity for tokens; it does not have to be a publicly reachable URL.

Environment variables

Where the file lives

Workflow File Location Local .env Repository root (next to docker-compose.yml) Production (Compose CLI) stack.env Same directory as the prod compose file you run Production (Portainer) Stack environment Set in the Portainer UI (or paste the same keys)
Compose automatically loads .env for variable substitution and, in this project, services also mount env_file: ./.env (local) or stack.env (prod example).

Example .env / stack.env

Use strong unique passwords. Do not commit real secrets.

Variable reference

Variable Required Default / notes MYSQL_ROOT_PASSWORD Yes MySQL root password DB_PASS Yes Password for DB_USER (API connects with this) DB_HOST No db DB_PORT No 3306 (host publish in prod compose uses ${DB_PORT:-3306}) DB_NAME No snippy DB_USER No snippy_api DB_SSL No unset / false — set true only for a remote MySQL that requires TLS (not Docker MySQL on the compose network) AUTH0_DOMAIN Yes (login) Auth0 tenant domain AUTH0_CLIENT_ID Yes (login) SPA Client ID (written into frontend env.js) AUTH0_AUDIENCE No http://localhost:3000 — must match Auth0 API Identifier FRONTEND_URL Strongly recommended http://localhost:4200 — must match the browser origin or CORS will block the API API_PORT No Host port mapped to API 3000 FRONTEND_PORT No (prod) Host port mapped to frontend nginx 8080 (prod example default 4200:8080) ENABLE_MINIO No false — set true only when MinIO is running and reachable as minio:9000 MINIO_* When MinIO enabled Root/app credentials, bucket name/policy, endpoint

How runtime config reaches the frontend

Local: snippy/frontend/entrypoint.dev.sh writes public/env.js from env vars before ng serve.
Production image: snippy/frontend/entrypoint.sh writes /usr/share/nginx/html/env.js on container start and selects nginx config with or without MinIO proxying.
After changing Auth0 or MinIO-related env vars, restart the frontend container so env.js is regenerated.

Local development (testing)

This is the path for day-to-day development and local smoke testing with hot reload.

What you get

From docker-compose.yml:
db — mysql:8.0 with snippy/db/init.sh mounted into /docker-entrypoint-initdb.d
api — built from snippy/backend/Dockerfile.dev, npm run dev (ts-node-dev), source bind-mounted, port 3000
frontend — built from snippy/frontend/Dockerfile.dev, ng serve with proxy (proxy.conf.json), port 4200
minio — commented out by default (asset uploads disabled unless you enable the services and ENABLE_MINIO=true)
The Angular dev server proxies:
/api → http://api:3000
/content → http://minio:9000 (only useful if MinIO is running)

Step-by-step

Clone the repository

Create Auth0 resources as described in Auth0 setup (local callback/logout/web origin URLs).
Create .env in the repo root using the example. At minimum set:
MYSQL_ROOT_PASSWORD
DB_PASS
AUTH0_DOMAIN
AUTH0_CLIENT_ID
AUTH0_AUDIENCE (match your Auth0 API Identifier)
FRONTEND_URL=http://localhost:4200
Start the stack

First run builds the API and frontend dev images and initializes MySQL. That can take several minutes.
Open the app
API (direct): http://localhost:3000 Browser traffic normally goes through the frontend proxy as /api/....
Log in with Auth0 from the landing page (“Get Started”).

Useful local commands


Root npm scripts for format, OpenAPI, and CI parity: Before a merge request.

Optional: enable MinIO locally

Uncomment the minio and minio-init services (and minio_data volume) in docker-compose.yml.
Set MinIO variables in .env and ENABLE_MINIO=true.
Run docker compose up --build.
Asset uploads work when MinIO is enabled: library upload/list/delete, insert into HTML/CSS/JS from the editor Assets dialog, profile pictures, and snapshot-on-save.

Optional: smoke-test production Dockerfiles locally

Production images are normally built by GitHub Actions. To build them yourself without publishing:

Prefer Production deployment with published Hub tags for a full runtime test.

Before a merge request

The root package.json wraps backend and frontend scripts so you can format, regenerate the OpenAPI client, and run the same checks as .github/workflows/ci.yml before you open a PR.
Install package dependencies once (CI does npm ci in each folder):

Prepare (writes files — commit what you intend to land):

Run generate:api when routes, DTOs, or openapi-definition.ts changed. Commit both openapi.json files and snippy/frontend/src/app/api/generated/.
Verify (must pass before you open the PR — this is what CI runs):

check:all stops on the first failure. It runs, in CI order:
Step Script What it does Audit check:audit npm audit --audit-level=high in backend and frontend Format check:format Prettier check (does not write) Types check:types tsc --noEmit (backend) and tsc -p tsconfig.app.json (frontend) OpenAPI check:openapi Regenerates spec + client, then git diff --exit-code Build check:build Backend tsc build and frontend production ng build Tests check:test Backend Vitest (npm test)
Other root scripts:
Script Purpose npm run dev:backend / dev:frontend Run API or Angular outside Docker npm run build:backend / build:frontend Build one package npm run test:all Backend Vitest and frontend Karma (not in CI; Karma may need a browser) npm run docker:compose:up docker compose build && up npm run docker:compose:clear Wipe volumes, rebuild, and start

Production deployment (Docker Hub)

Production uses pre-built images published to Docker Hub as kennyl777/snippy-*. Images are built by the manual GitHub Actions workflow .github/workflows/docker-image.yml (workflow_dispatch), which pushes both version tags (from package.json) and :latest.
Published images:
Image Purpose kennyl777/snippy-api:latest API (compiled Node) kennyl777/snippy-frontend:latest Angular SPA served by nginx kennyl777/snippy-db:latest MySQL 8 + init script kennyl777/snippy-minio:latest One-shot MinIO bucket/user bootstrap
Use docker-compose.prod.example.yml as your starting point. That file:
Pulls Hub images (does not build from source)
Expects env file name stack.env
Attaches services to an external Docker network named NPM
Includes MinIO + minio-init
Maps frontend host port ${FRONTEND_PORT:-4200} → container 8080

Important production requirements

HTTPS / secure origin for Auth0 (see Auth0 and HTTPS).
FRONTEND_URL must be the exact public origin users type into the browser (scheme + host, no path), or CORS will fail.
Auth0 callback / logout / web origin URLs must match that same origin.
Create the external NPM network before starting the stack (see below), even if you are not using Nginx Proxy Manager yet—or edit the compose file to use a different network.

Option A — Docker Compose CLI

Copy the example compose file (optional rename):

Create stack.env next to that compose file with production values. Example differences from local:

Create the external network (once per Docker host):

Pull and start

Put TLS in front of the frontend (recommended): point Nginx Proxy Manager (or another reverse proxy) at the snippy-frontend container on port 8080 (or whatever host port you published). See Nginx Proxy Manager setup.
Verify

Open your HTTPS URL, confirm login works, and create a test snippet.

Option B — Portainer stacks

On the Docker host, create the external network if needed:

In Portainer: Stacks → Add stack.
Paste the contents of docker-compose.prod.example.yml (or upload the file).
Add the same keys as in stack.env under the stack Environment variables UI. If Portainer still expects a file named stack.env because of env_file: stack.env in the compose, either:
place a stack.env on the host path Portainer uses for that stack, or
adjust the compose env_file entries to match how you inject env in Portainer.
Deploy the stack.
Configure Nginx Proxy Manager to proxy your domain to snippy-frontend:8080 on the NPM network (container-to-container), or to the published host port if you prefer.

Production port map (defaults from the example file)

Service Container port Default host publish frontend 8080 4200 (FRONTEND_PORT) api 3000 3000 (API_PORT) db 3306 3306 (DB_PORT) minio API 9000 32570 minio console 9001 32571
In a typical NPM setup you do not need to expose API/DB publicly; the frontend nginx proxies /api/ to http://api:3000 on the Docker network.

First-boot database notes

MySQL data lives in the mysql-data volume.
init.sh runs only on first initialization of an empty data directory.
Changing DB_PASS later does not automatically update an already-created MySQL user—plan passwords before first boot, or reset the volume (docker compose ... down -v) knowing that deletes data.

Nginx Proxy Manager setup

Use this when you want HTTPS (Let’s Encrypt or self-signed) in front of Snippy, especially for Auth0 on non-localhost hosts.

1. Create the NPM network


Snippy’s production compose attaches to this network as external: true.

2. Deploy Nginx Proxy Manager (example)

Example Portainer / Compose stack:

Admin UI: http://<docker-host>:81
Default login is documented by the Nginx Proxy Manager project — change it immediately.

3. Proxy host for Snippy

Ensure the Snippy stack is on the same NPM network.
In NPM, create a Proxy Host:
Domain: snippy.example.com
Scheme: http
Forward hostname: snippy-frontend (container name) or the Docker host IP if forwarding to a published port
Forward port: 8080 (container) or your published FRONTEND_PORT
Enable SSL (Let’s Encrypt or custom/self-signed).
Confirm Auth0 Application URIs and FRONTEND_URL use https://snippy.example.com.

Self-signed / LAN-only note

If you only need HTTPS on a home LAN, a self-signed cert in NPM is enough for Auth0’s secure-origin requirement, but browsers will warn until you trust the certificate.

Updating production images

When new images are published to Docker Hub:

In Portainer: update the stack / pull and redeploy so containers recreate on :latest (or pin specific version tags from Docker Hub / package.json versions for more control).
Current version sources:
API: snippy/backend/package.json
Frontend: snippy/frontend/package.json
DB / MinIO init images: tagged latest (and version when available) by the workflow

Troubleshooting

Symptom Likely cause What to try Login button does nothing / Auth0 error Missing or wrong AUTH0_DOMAIN / AUTH0_CLIENT_ID, or callback URL mismatch Fix .env / stack.env, restart frontend, verify Auth0 URIs exactly match the browser URL Auth0 “secure origin” / callback failures on LAN IP Using http://192.168.x.x Put HTTPS in front (NPM) and use a hostname API calls fail with CORS errors FRONTEND_URL ≠ browser origin Set FRONTEND_URL to exact origin (https://snippy.example.com), restart api Frontend loads but API 502/404 API not healthy, or nginx/proxy misconfigured docker compose logs api; confirm frontend can reach api:3000 on the Docker network API crashes on boot Missing DB_PASS or AUTH0_DOMAIN Required by API config validation — set them and recreate the API container DB init / auth failures Wrong MYSQL_ROOT_PASSWORD / DB_PASS, or volume from older credentials Align env with existing volume, or down -v for a clean DB (destructive) network NPM declared as external, but could not be found Network not created docker network create NPM MinIO / assets unavailable ENABLE_MINIO false, MinIO not running, or MinIO process exited Give the minio service restart: unless-stopped (Portainer). Healthcheck looks for port 9000 in /proc/net/tcp (works without curl/bash/wget). After a connection failure the API latches MinIO off until API restart; the SPA probes GET /api/v1/health and latches Assets / Profile Image / snapshot-on-save until you restart the stack and reload minio/mc:latest / minio/minio:latest pull denied MinIO removed community images from Docker Hub Use cgr.dev/chainguard/minio and cgr.dev/chainguard/minio-client (already wired in compose + snippy/minio/Dockerfile) Env changes ignored in browser Stale env.js Restart frontend container and hard-refresh the browser

Project layout


Documentation

Doc Contents documentation/frontend.md Angular architecture, editor preferences, themes how-to, embed player documentation/backend.md API layers, auth, PUT /users + editorPreferences documentation/db.md Schema (including users.editor_preferences) documentation/openapi.json Exported OpenAPI snapshot. From the repo root: npm run generate:api (dumps spec + generates the SPA client) documentation/frontend-test-plan.md Manual QA checklist
In-app legal pages (when the frontend is running):
Privacy Policy: /privacy
Terms and Conditions: /terms
Footer links also point at the GitHub repo and MIT license.

License and legal

Snippy source code is released under the MIT License — Copyright (c) Kenneth Lamb.
The hosted service terms and privacy practices are described in the application’s Terms and Privacy Policy pages. Self-hosted operators are responsible for their own infrastructure, backups, TLS, and compliance.
Like this project

Posted Sep 27, 2026

Developed an open-source CodePen-style app for HTML, CSS, JS snippets with Docker support.