{"slug": "show-hn-hycanvas-a-free-self-hostable-canva-alternative", "title": "Show HN: HyCanvas, a free self-hostable Canva alternative", "summary": "HyCanvas, a free self-hostable AI-native design platform, has been released as an open-source alternative to Canva. The platform supports social graphics, presentations, videos, whiteboards, docs, and print with no paywalls or watermarks, and uses a Go backend with a Next.js frontend, PostgreSQL, and optional S3-compatible storage.", "body_md": "HyCanvas is a free, self-hostable, AI-native design platform. Design anything - social graphics, presentations, videos, whiteboards, docs, and print - with no paywalls or watermarks. Web-only.\n\nThe product spans a single-player editor, document types (presentations, video, whiteboard, docs, sheets), export, brand kits, and a bring-your-own-key AI layer. Work not yet built is tracked under `docs/roadmap/`\n\n.\n\nOpen a template and make it yours - edit text, restyle, and export, all in the browser:\n\nEvery design stays yours in an open format: download any document as a portable `.hyc`\n\nfile (readable JSON) and import it on any instance, or export to PNG, PDF, and more, with no watermarks:\n\nThe frontend and shared packages are an npm-workspaces monorepo (orchestrated with concurrently and dotenv-cli against a single shared root `.env`\n\n); the backend is a standalone Go module.\n\n`backend`\n\n- Go backend (REST under`/api/v1`\n\n, the`/realtime`\n\nWebSocket, the Go rendering engine for export, and DB migrations). Serves the exported frontend in the production bundle. Postgres only.`frontend`\n\n- Next.js app (Pages Router), statically exported for production.`packages/*`\n\n- framework-agnostic`@hc/*`\n\nlibraries (schema, engine, editor, sdk, color, text, geometry, export, media, stock, templates, authz, formula, sheets, timeline, whiteboard, docs, publishing, website, print, a11y, ...). The frontend imports them from their built`dist/`\n\n.`scripts/build-dist.js`\n\n- embeds the exported frontend into the Go binary (`go build -tags embed`\n\n) and writes the single self-contained`dist/hycanvas`\n\n.\n\n- illustrated user and operator guides:`docs/`\n\n[getting started](/hyscaler/HyCanvas/blob/development/docs/getting-started.md),[the dashboard](/hyscaler/HyCanvas/blob/development/docs/dashboard.md),[the editor](/hyscaler/HyCanvas/blob/development/docs/editor.md),[document types](/hyscaler/HyCanvas/blob/development/docs/document-types.md), and the[first-run setup wizard](/hyscaler/HyCanvas/blob/development/docs/setup-wizard.md)with every step screenshotted.`docs/roadmap/`\n\n- forward-looking specs for work not yet built (realtime collaboration, AI media, accessibility/i18n/enterprise).`CLAUDE.md`\n\n- working guidance for this repository.\n\n- Node 24 (see\n`.nvmrc`\n\n) for the frontend and shared packages. - Go 1.25 for the backend (\n`backend`\n\n). - PostgreSQL. Object storage is optional (S3-compatible / MinIO); the backend falls back to local-file storage.\n- ffmpeg only if you want server-side video export (already bundled in the Docker image).\n\n```\ncp .env.example .env        # then edit values (at minimum DATABASE_URL + JWT_SECRET)\nnpm install                 # installs the frontend + shared packages\nnpm run build:packages      # build the @hc/* libraries once (the frontend imports their dist)\nnpm run db:migrate          # apply SQL migrations (Go migrator)\nnpm run dev                 # backend (Go) on :8005, frontend on :3000\n```\n\nOpen `http://localhost:3000`\n\n. The frontend reads the backend base URL from `NEXT_PUBLIC_BACKEND_URL`\n\n(defaults to `http://localhost:8005/api`\n\n).\n\nNotes for development:\n\n`npm run build:packages`\n\nis required before the first`npm run dev`\n\n(and after editing any`packages/*`\n\nsource), because the`@hc/*`\n\npackages are consumed from their compiled`dist/`\n\n, not their source.`npm run build`\n\nand`npm run build:dist`\n\nbuild the packages for you.- The server also runs migrations on boot when\n`DB_AUTO_MIGRATE=true`\n\n, so`npm run db:migrate`\n\nis mainly for an explicit, pre-boot migration. - No SMTP is wired: verify-email / password-reset / magic-link links are read from the dev outbox in non-production instead of being emailed.\n\nThe production bundle is a single self-contained binary: `dist/hycanvas`\n\n. The statically-exported frontend is baked into it (`go:embed`\n\n, built with `-tags embed`\n\n), so one binary serves the frontend, the REST API, and the realtime WebSocket on one port, with no Node runtime and no sidecar `public/`\n\nfolder. It migrates the database on boot. The build also stamps the git version into the binary; it is logged on startup and returned by `/healthz`\n\nand `/api/v1/_go/health`\n\n.\n\n```\nnpm run build:dist          # builds @hc/* + the frontend (routed to /api), embeds it, and compiles the Go binary into dist/\n./dist/hycanvas       # run it directly: the binary loads .env itself, no Node needed\n```\n\nRunning the bundle needs no Node at all: the Go binary loads a `.env`\n\nfrom the working directory (or its parent) on startup, so `./dist/hycanvas`\n\nis fully standalone, exactly how it runs under Docker. Real environment variables always win over `.env`\n\n, so injected config (containers, CI) is never overridden. `npm run start:dist:only`\n\nis just a convenience alias for the same binary.\n\nIf you build the binary without the embedded UI (for example a plain `npm run build:backend`\n\n), set `PUBLIC_DIR`\n\nto an exported frontend directory to serve it; with neither, the binary serves the API only and shows a short notice page.\n\nIn the dist build the frontend talks to the same-origin `/api`\n\n(no `NEXT_PUBLIC_BACKEND_URL`\n\nneeded), so the one process answers UI and API together.\n\nThe binary can also run itself as a background service, so no external process manager is needed. `./dist/hycanvas start`\n\n(or just `./dist/hycanvas`\n\n) runs in the foreground; the `service`\n\nverbs manage a detached process with a pidfile and logfile next to the binary, on Linux, macOS, and Windows alike:\n\n```\n./dist/hycanvas service start       # detach into the background\n./dist/hycanvas service status\n./dist/hycanvas service log         # last log lines; -f follows\n./dist/hycanvas service restart\n./dist/hycanvas service stop\n```\n\nThe binary's directory is the service's working directory, which is where `.env`\n\nis read from. The service does not auto-start at boot; if you want that, add a crontab entry (`@reboot /path/to/hycanvas service start`\n\n) or use Docker.\n\nNo `.env`\n\nyet? Just start the server. On an interactive terminal it first asks whether to set up in the browser or right there in the terminal:\n\n**Web wizard**(default): the server boots into setup mode and prints a one-time wizard access secret; opening any page redirects to`/installation/step-1`\n\n, which asks for that secret and then walks through PostgreSQL, storage (local or S3), and optional SMTP, testing each answer live with visible progress. Step 1 includes \"Running HyCanvas behind a proxy?\": when enabled you configure the external domain (e.g.`https://hycanvas.art`\n\n) separately from the internal host and port the proxy forwards to (written as`APP_URL`\n\n,`BIND_HOST`\n\n, and`PORT`\n\n). Answers are held on the server (never in the browser), and a page refresh always restarts at the welcome step.**CLI wizard**: the same questions asked in the terminal, with the same live validation and hidden password input.\n\nEither way the wizard writes `.env`\n\n(secrets like `JWT_SECRET`\n\nare generated automatically), runs the database migrations, starts the app in the same process, and (in the browser flow) creates your first account. Non-interactive starts (Docker, pipes) default to the web wizard. To skip all of it, create a `.env`\n\nby hand (see `.env.example`\n\n) before starting.\n\n`npm run deploy`\n\nrebuilds the bundle and restarts the running service in one step (`build:dist`\n\n+ `./dist/hycanvas service restart`\n\n).\n\nWhen writing `.env`\n\nby hand for production, set at minimum `NODE_ENV=production`\n\n, `DATABASE_URL`\n\n, a strong `JWT_SECRET`\n\n, `APP_URL`\n\n(public base URL used in generated links), and an absolute `LOCAL_STORAGE_PATH`\n\n(or `STORAGE_DRIVER=s3`\n\nwith `S3_*`\n\n). AI provider keys are configured per workspace at runtime (stored encrypted), never via env.\n\nHyCanvas serves plain HTTP; put nginx, Caddy, or Traefik in front for TLS. Three settings matter: `APP_URL`\n\nis the external domain the proxy serves (used in generated links and the OIDC redirect), `PORT`\n\nis the internal port the proxy forwards to, and `BIND_HOST=127.0.0.1`\n\nkeeps the app reachable only through the proxy. The setup wizard configures all three when you answer \"Running HyCanvas behind a proxy?\" in step 1. The proxy must forward the `Host`\n\nheader and (for realtime collaboration) WebSocket upgrades on `/realtime`\n\n. With an https `APP_URL`\n\n, session cookies stay `Secure`\n\nautomatically.\n\nAlso raise the proxy's request-body limit: uploads travel as base64 JSON, about a third larger than the file on disk, and nginx's default `client_max_body_size`\n\nof 1 MB rejects anything bigger with a 413 before HyCanvas ever sees it. Set it to comfortably above the largest upload you expect, for example `client_max_body_size 100m;`\n\n. Caddy and Traefik impose no body limit by default.\n\nSocial sign-in is configured via env and appears on the login and signup pages once set. For Google:\n\n- In\n[Google Cloud Console](https://console.cloud.google.com/apis/credentials), configure the OAuth consent screen, then create an OAuth client ID of type \"Web application\". - Add the authorized redirect URI:\n`<APP_URL>/api/v1/auth/oidc/callback`\n\n(for example`https://hycanvas.art/api/v1/auth/oidc/callback`\n\n). - Set in\n`.env`\n\nand restart (`./dist/hycanvas service restart`\n\n):\n\n```\nOIDC_ISSUER=https://accounts.google.com\nOIDC_CLIENT_ID=<client id>.apps.googleusercontent.com\nOIDC_CLIENT_SECRET=<client secret>\nOIDC_LABEL=Google\n```\n\nA \"Continue with Google\" button then shows on the auth pages. Any standards-compliant OIDC provider works the same way via its issuer URL; `OIDC_ALLOWED_EMAIL_DOMAINS`\n\nrestricts sign-in to listed domains, and `OIDC_REDIRECT_URI`\n\noverrides the callback when the default does not fit. The issuer must be https (localhost excepted), and `APP_URL`\n\nmust be set correctly, especially behind a proxy.\n\nThree sign-in methods ship (email + password, magic link, and OIDC/SSO), and each has an independent login and signup toggle so you can decide exactly how people get in and how accounts are created. Set any of these to `true`\n\nor `false`\n\n; anything else keeps the default. The defaults leave the product as it was before these settings existed, so you only set what you want to change.\n\n| Variable | Default | Effect |\n|---|---|---|\n`AUTH_PASSWORD_LOGIN_ENABLED` |\n`true` |\nSign in with email + password |\n`AUTH_PASSWORD_SIGNUP_ENABLED` |\n`true` |\nCreate an account with a password |\n`AUTH_MAGICLINK_LOGIN_ENABLED` |\n`true` |\nEmailed sign-in link for an existing account |\n`AUTH_MAGICLINK_SIGNUP_ENABLED` |\n`false` |\nEmailed link that creates a new account (create-on-click; nothing is created until the link is opened) |\n`AUTH_OIDC_LOGIN_ENABLED` |\n`true` |\nSSO sign-in (needs the `OIDC_*` settings above) |\n`AUTH_OIDC_SIGNUP_ENABLED` |\n`true` |\nSSO creates a new account on first login |\n\nThe sign-in page shows only the methods you leave on, so no user sees a form they cannot use. The toggles are enforced on the server too: a disabled method's endpoint returns 403, not just a hidden button.\n\nTwo guardrails apply automatically. The OIDC toggles do nothing unless OIDC is configured. And if a combination would leave no usable way to sign in, the server keeps password login on and logs a warning, so a typo cannot lock you out.\n\nTo run an instance where every account comes from your identity provider (no passwords, no magic links), turn the local methods off and leave OIDC on:\n\n```\nAUTH_PASSWORD_LOGIN_ENABLED=false\nAUTH_PASSWORD_SIGNUP_ENABLED=false\nAUTH_MAGICLINK_LOGIN_ENABLED=false\n# OIDC_* configured as above; AUTH_OIDC_* left at their true default\n```\n\nNote when switching an existing instance to SSO-only: a user who already has a password account links their SSO identity to it automatically only when the provider's email domain is listed in `OIDC_ALLOWED_EMAIL_DOMAINS`\n\n(you are vouching that the provider owns those emails). Without that, their first SSO login creates a separate account. Set the allowlist before flipping the switch if you have existing password users.\n\nProtect login, signup, password-reset request, and magic-link request from automated abuse with Cloudflare Turnstile or Google reCAPTCHA. It is off until you set a provider:\n\n```\nCAPTCHA_PROVIDER=turnstile          # or \"recaptcha\"\nCAPTCHA_SITE_KEY=<public site key>  # from the provider dashboard\nCAPTCHA_SECRET_KEY=<secret key>\n# CAPTCHA_MIN_SCORE=0.5             # reCAPTCHA v3 only; Turnstile / v2 ignore it\n```\n\nThe site key is sent to the browser (public by design) and the sign-in page renders the widget; the secret verifies each response server-side. The redeem endpoints (password-reset and magic-link, which carry a token from an email, not a form) are not gated.\n\nTwo behaviors to know. It is **fail-closed**: if the provider is unreachable, the gated forms reject, so a solved challenge is always required once enabled; a provider outage blocks these forms, which is the correct default for a security control but is worth planning for. And a provider set without both keys is treated as disabled (and logged), so a half-configuration never 403s every login. Both instances behind Cloudflare make Turnstile the natural choice; reCAPTCHA works the same way via its keys.\n\nStarted on local-disk storage and want object storage later? The binary migrates itself:\n\n```\n./dist/hycanvas service stop       # stop first so no new objects land mid-copy\n./dist/hycanvas storage migrate    # copies every object, verifies, updates .env\n./dist/hycanvas service start\n```\n\nThe S3 target (AWS, MinIO, R2, ...) is taken from `S3_*`\n\nin the environment when present, or asked interactively with a connectivity check; `--dry-run`\n\npreviews the object count and size, and `--yes`\n\nmakes it non-interactive for scripts. The copy is idempotent (already-present objects are skipped, so re-runs are safe), the database needs no changes (it stores storage keys, not URLs), and the local files are kept as a rollback until you delete them.\n\nReleases on the [GitHub releases page](https://github.com/hyscaler/HyCanvas/releases) ship the same self-contained binary prebuilt for Linux (amd64, arm64), macOS (Intel, Apple Silicon), and Windows (amd64). Each archive contains just the `hycanvas`\n\nbinary (the first-run wizard generates the configuration), and a `SHA256SUMS.txt`\n\naccompanies the archives for verification. You still need PostgreSQL; ffmpeg is only required for server-side video export.\n\n```\ntar -xzf hycanvas_<version>_<os>_<arch>.tar.gz && cd <unpacked dir>\n./hycanvas service start    # asks: browser wizard or terminal wizard\n```\n\nThe macOS binaries are not signed or notarized; if macOS quarantines the download, run `xattr -d com.apple.quarantine hycanvas`\n\nor right-click the binary and choose Open.\n\nThe whole product (UI + REST API + realtime WebSocket) runs from one image, with Postgres as a companion service. For the full environment-variable reference and production options, see [DOCKER_SETUP.md](/hyscaler/HyCanvas/blob/development/DOCKER_SETUP.md).\n\nThe repo ships a `docker-compose.yml`\n\nthat pulls the prebuilt image (`linux/amd64`\n\nand `linux/arm64`\n\n) and runs it against your own managed Postgres (it ships no bundled `db`\n\n):\n\n```\ncp .env.example .env   # set JWT_SECRET, APP_PORT, and a reachable DATABASE_URL\ndocker compose up -d\n```\n\nThen open `http://localhost:<APP_PORT>`\n\n(e.g. `http://localhost:8005`\n\n). `JWT_SECRET`\n\n, `APP_PORT`\n\n, and a reachable `DATABASE_URL`\n\n(or `EXTERNAL_DATABASE_URL`\n\n) are required and migrations run on boot; inside a container `localhost`\n\nis the container, so use `host.docker.internal`\n\nfor a DB on your machine (mapped via `extra_hosts`\n\n) or a managed endpoint. Update later with `docker compose pull && docker compose up -d`\n\n. If you'd rather have Postgres bundled for a quick trial, the self-contained compose in [docker/README.md](/hyscaler/HyCanvas/blob/development/docker/README.md) includes a `db`\n\nservice and runs anywhere.\n\nTo run your own source instead of the published image, use the build variant (it builds `Dockerfile`\n\nand adds a container healthcheck + restart policy):\n\n```\ndocker compose -f docker-compose.prod.yml up --build -d\n```\n\nThe build variant serves at `http://localhost:8005`\n\n; the published `docker-compose.yml`\n\npublishes on the host port you set in `APP_PORT`\n\n. Stop with `docker compose down`\n\n. The build variant can run a bundled Postgres (`COMPOSE_PROFILES=bundled`\n\n); add `-v`\n\nto drop that Postgres volume.\n\nNotes:\n\n- ffmpeg (for video export) is bundled in the image; no host install needed.\n- Local-file storage persists on the host at\n`./data/storage`\n\n(bind-mounted to`/app/.data/storage`\n\n) with the published`docker-compose.yml`\n\n, so it survives`docker compose down -v`\n\n. To use object storage instead, set the`S3_*`\n\nvariables on the`app`\n\nservice. - Secrets/config (notably\n`JWT_SECRET`\n\n, and optionally`AI_SECRET`\n\n,`OIDC_*`\n\n,`VAPID_*`\n\n) come from your`.env`\n\n. - Database: the published\n`docker-compose.yml`\n\nalways uses an external Postgres, set`DATABASE_URL`\n\nor`EXTERNAL_DATABASE_URL`\n\nin`.env`\n\n. The build-from-source`docker-compose.prod.yml`\n\ncan bundle Postgres with`COMPOSE_PROFILES=bundled`\n\n, or point it external by emptying`COMPOSE_PROFILES`\n\nand setting`EXTERNAL_DATABASE_URL`\n\n. For Postgres on the host, use`host.docker.internal`\n\n(both compose files map it via`extra_hosts`\n\n).\n\nTo work on the code in containers, with no local Go or Node toolchain, use the dev stack. It runs the Go backend on `:8005`\n\nand the Next.js dev server on `:3000`\n\nwith live reload against your working tree:\n\n```\ndocker compose -f docker-compose.dev.yml up --build\n```\n\nOpen `http://localhost:3000`\n\n. See [DOCKER_SETUP.md](/hyscaler/HyCanvas/blob/development/DOCKER_SETUP.md#option-c-local-development) for details.\n\nAll configuration is read from the root `.env`\n\n(copy `.env.example`\n\n). The most important keys:\n\n| Variable | Mode | Description |\n|---|---|---|\n`DATABASE_URL` |\nboth | Postgres connection string. Required. |\n`JWT_SECRET` |\nboth | Signs session tokens (also the fallback key for encrypting AI keys). Use a strong value in production. |\n`PORT` |\nboth | Backend port (default `8005` ). |\n`NODE_ENV` |\nboth | `development` or `production` . |\n`DB_AUTO_MIGRATE` |\nboth | When `true` , the server applies migrations on boot. |\n`NEXT_PUBLIC_BACKEND_URL` |\ndev | Frontend API base in dev (`http://localhost:8005/api` ). Unused in the dist build (it calls same-origin `/api` ). |\n`FRONTEND_URL` |\nboth | Allowed CORS origin for the API (dev: `http://localhost:3000` ). |\n`APP_URL` |\nprod | Public base URL used in generated links (verify-email, magic-link, share). |\n`STORAGE_DRIVER` |\nboth | `local` (default) or `s3` . |\n`LOCAL_STORAGE_PATH` |\nboth | Absolute path for local-file storage when `STORAGE_DRIVER=local` . Must be absolute so dev and the dist bundle share it. |\n`S3_*` |\noptional | S3-compatible object storage (endpoint, bucket, keys) when `STORAGE_DRIVER=s3` . |\n`ASSET_QUOTA_BYTES` |\noptional | Per-workspace upload cap in bytes (default 2 GiB). |\n`USER_STORAGE_QUOTA_BYTES` |\noptional | Global per-user upload cap across all workspaces; unset/0 = unlimited. For public instances. |\n`AI_SECRET` |\noptional | Encrypts stored per-workspace AI keys; falls back to `JWT_SECRET` . |\n`OIDC_*` |\noptional | OIDC single sign-on (issuer, client id/secret). |\n`AUTH_*_ENABLED` |\noptional | Enable/disable each sign-in method's login and signup (password, magic link, OIDC). Defaults preserve prior behavior. See\n|\n\n`CAPTCHA_*`\n\n`CAPTCHA_PROVIDER`\n\n, `CAPTCHA_SITE_KEY`\n\n, `CAPTCHA_SECRET_KEY`\n\n, `CAPTCHA_MIN_SCORE`\n\n). Off unless a provider is set. See [CAPTCHA on the auth forms](#captcha-on-the-auth-forms).`GOOGLE_ANALYTICS_ID`\n\n`G-XXXXXXXXXX`\n\n). When set, the server injects the gtag.js snippet into served HTML pages at runtime; empty means no analytics.`VAPID_*`\n\n`POSTGRES_USER`\n\n/ `POSTGRES_PASSWORD`\n\n/ `POSTGRES_DB`\n\n/ `COMPOSE_PROFILES`\n\n`docker-compose.yml`\n\n.| Command | Description |\n|---|---|\n`npm run dev` |\nRun the Go backend and the frontend together with hot reload. |\n`npm run build:packages` |\nBuild the `@hc/*` libraries (needed before dev on a fresh checkout). |\n`npm run build` |\nBuild packages, the Go binary, and the frontend. |\n`npm run gen:theme` |\nRegenerate color tokens from `frontend/src/theme.config.mjs` (the frontend build runs this automatically). |\n`npm run db:migrate` |\nApply SQL migrations (Go migrator). The server also migrates on boot. |\n`npm run lint` |\nVet the Go backend and lint the frontend. |\n`npm run test` |\nRun the package and Go backend tests. |\n`npm run build:dist` |\nCompile the single `dist/hycanvas` binary with the frontend embedded (`-tags embed` ) and the git version stamped in. |\n`npm run start:dist:only` |\nRun the already-built binary (alias for `./dist/hycanvas` , which loads `.env` itself). |\n`npm run deploy` |\nBuild the dist bundle and restart the built-in service (`hycanvas service restart` ). |\n\nThe starter template catalog lives in `backend/internal/templates/seed.json`\n\nand is compiled into the binary (`//go:embed`\n\n). There is no seed command or database step: the templates are served directly from the embedded JSON, merged with any templates saved to the database.\n\nTo add or edit a built-in template, edit `seed.json`\n\n, then rebuild/restart the backend so it re-embeds the file:\n\n- Development: restart\n`npm run dev`\n\n(it runs the backend via`go run`\n\n, which recompiles each start). - Production (native):\n`npm run build:dist`\n\nthen`npm run start:dist:only`\n\n(or`npm run deploy`\n\n). - Docker: rebuild from source with\n`docker compose -f docker-compose.prod.yml up --build`\n\n(the default`docker compose up`\n\npulls the published image and won't include local edits).\n\nUser-saved templates (Save as template) are stored in the database and need no rebuild.\n\nThe app's color identity lives in one file: `frontend/src/theme.config.mjs`\n\n(the brand and accent scales, the identity gradient, the editor canvas-overlay colors, and the collaborator presence palette). To rebrand, edit that file and run `npm run gen:theme`\n\n. The generator rewrites the Tailwind CSS tokens, the typed canvas-overlay constants, and the Go presence palette, so one change propagates across the UI chrome, the gradient, the logo, the favicon/theme-color, the canvas overlays, and presence colors. `npm run gen:theme:check`\n\n(run as part of `npm run lint`\n\n) fails if the committed generated files drift from the source.\n\nThis product/app accent is intentionally separate from the per-workspace Brand Kit, which themes design content rather than the app shell.\n\nTwo long-lived branches: `development`\n\n(ongoing work) and `stable`\n\n(release-ready). Pushing commits builds no Docker images and cuts no releases; CI runs tests only. Releasing is explicit:\n\n- Merge\n`development`\n\ninto`stable`\n\nwhen it is release-ready. - Tag on\n`stable`\n\n:`git tag v0.2.0 && git push origin v0.2.0`\n\n. The release workflow REFUSES tags whose commit is not on`stable`\n\n.\n\nOne tag then drives everything (`.github/workflows/release.yml`\n\n):\n\n**Binary release**: the frontend builds once, the embedded binary cross-compiles for all five platforms (pure Go,`CGO_ENABLED=0`\n\n), and a GitHub Release is published with the archives and`SHA256SUMS.txt`\n\n. Hyphenated tags (`v0.2.0-rc.1`\n\n) are marked pre-release.**Docker images**, always lean (the`docker`\n\njob packages the just-built release binaries into the runtime-only`Dockerfile.release`\n\n; no toolchains, no from-source image builds anywhere):- a final tag\n`v0.2.0`\n\npublishes`hycanvas/hycanvas:latest`\n\n,`:development`\n\n,`:0.2.0`\n\n, and the rolling minor alias`:0.2`\n\n; - a pre-release tag\n`v0.2.0-rc.1`\n\npublishes`:development`\n\nand`:0.2.0-rc.1`\n\nonly, so the development channel previews the next release while`latest`\n\nstays on the last final one.\n\n- a final tag\n**Docker Hub overview**: any change to`docker/README.md`\n\non`development`\n\nis synced to the Docker Hub repository description automatically by`.github/workflows/dockerhub-description.yml`\n\n(also runnable manually from the Actions tab).\n\nThe Actions \"Run workflow\" button on the release workflow does a build-only dry run without releasing or publishing images.\n\nThe version stamped into the binary (`git describe`\n\n) is logged on startup and reported by `/healthz`\n\n.\n\n- TypeScript everywhere, strict mode. Keep the rendering engine free of any React or UI dependency.\n- For shipped features, match the surrounding code; for unbuilt work, read the relevant\n`docs/roadmap/`\n\nspec first. - Never use longdash characters or standalone three-hyphen horizontal rules in markdown.\n\nSee `CONTRIBUTING.md`\n\nfor setup and the pull-request checklist, `CODE_OF_CONDUCT.md`\n\nfor community expectations, and `SECURITY.md`\n\nfor reporting vulnerabilities.\n\nHyCanvas is licensed under the Elastic License 2.0 (see `LICENSE`\n\n). This is a source-available license: you may use, modify, self-host, and redistribute it freely, but you may not provide it to third parties as a hosted or managed service, circumvent the license-key functionality, or remove the licensing notices. Third-party and bundled-asset notices are in `NOTICE`\n\n.\n\n**Hosting HyCanvas for your own customers?** That needs a[commercial license](/hyscaler/HyCanvas/blob/development/COMMERCIAL.md)from HyScaler; internal use and self-hosting never do.**Contributing?** All contributions require a one-time[Contributor License Agreement](/hyscaler/HyCanvas/blob/development/CLA.md), signed with a single comment on your first pull request.\n\n© 2026 HyScaler. HyCanvas is a HyScaler product. HyScaler® is a registered trademark.", "url": "https://wpnews.pro/news/show-hn-hycanvas-a-free-self-hostable-canva-alternative", "canonical_source": "https://github.com/hyscaler/HyCanvas", "published_at": "2026-07-21 05:19:26+00:00", "updated_at": "2026-07-21 05:53:24.037995+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["HyCanvas", "Canva", "Next.js", "PostgreSQL", "Go"], "alternates": {"html": "https://wpnews.pro/news/show-hn-hycanvas-a-free-self-hostable-canva-alternative", "markdown": "https://wpnews.pro/news/show-hn-hycanvas-a-free-self-hostable-canva-alternative.md", "text": "https://wpnews.pro/news/show-hn-hycanvas-a-free-self-hostable-canva-alternative.txt", "jsonld": "https://wpnews.pro/news/show-hn-hycanvas-a-free-self-hostable-canva-alternative.jsonld"}}