feat(docker/podman): self-hosting with docker/podman compose (#3312)
Some checks failed
Deploy to vercel on merge / build_and_deploy (push) Has been cancelled

* initial files

* added testing files

* removed unused files

* cleaned additional mounts

* fixed sql init

* removed more unused files

* moved to docker folder

* revert package.json

* gitignore update

* env example comments and compose necessary healthcheck

* ghcr package impl

* updated dockerfile steps  for layer caching

* added development-stage to dockerfile to dev environment

* added documentation on how to use dockerfile and compose.yml

* fixed prettier issues

* fixed image tag

* removed workflow for later
This commit is contained in:
Aniket Kotal 2026-02-18 18:58:10 +05:30 committed by GitHub
parent c6ae85484e
commit eec2c39f19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 580 additions and 12 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
docker/.env
# dependencies
/node_modules

View file

@ -1,20 +1,40 @@
FROM node:22-slim
ENV PNPM_HOME="/root/.local/share/pnpm"
ENV PATH="${PATH}:${PNPM_HOME}"
RUN npm install --global pnpm
COPY . /app
FROM docker.io/node:22-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@10.29.3 --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/readest-app/package.json ./apps/readest-app/
COPY patches/ ./patches/
COPY packages/ ./packages/
RUN pnpm install
FROM base AS dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm --filter @readest/readest-app setup-vendors
FROM dependencies AS development-stage
COPY . .
WORKDIR /app/apps/readest-app
EXPOSE 3000
ENTRYPOINT ["pnpm", "dev-web", "-H", "0.0.0.0"]
FROM base AS build
ARG NEXT_PUBLIC_SUPABASE_URL
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
ARG NEXT_PUBLIC_APP_PLATFORM
ARG NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_OBJECT_STORAGE_TYPE
ARG NEXT_PUBLIC_STORAGE_FIXED_QUOTA
ARG NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA
COPY --from=dependencies /app/node_modules /app/node_modules
COPY --from=dependencies /app/apps/readest-app/node_modules /app/apps/readest-app/node_modules
COPY --from=dependencies /app/apps/readest-app/public/vendor /app/apps/readest-app/public/vendor
COPY --from=dependencies /app/packages/foliate-js/node_modules /app/packages/foliate-js/node_modules
COPY . .
WORKDIR /app/apps/readest-app
RUN pnpm build-web
ENTRYPOINT ["pnpm", "start-web"]
FROM build as production-stage
ENTRYPOINT ["pnpm", "start-web", "-H", "0.0.0.0"]
EXPOSE 3000

View file

@ -1,9 +1,11 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl =
process.env['SUPABASE_URL'] ||
process.env['NEXT_PUBLIC_SUPABASE_URL'] ||
atob(process.env['NEXT_PUBLIC_DEFAULT_SUPABASE_URL_BASE64']!);
const supabaseAnonKey =
process.env['SUPABASE_ANON_KEY'] ||
process.env['NEXT_PUBLIC_SUPABASE_ANON_KEY'] ||
atob(process.env['NEXT_PUBLIC_DEFAULT_SUPABASE_KEY_BASE64']!);

51
docker/.env.example Normal file
View file

@ -0,0 +1,51 @@
# ip used by frontend/client-side urls to access the backend
HOST_IP=localhost
# db(psql) config
# change to strong password, min 32 chars
POSTGRES_PASSWORD=your-super-secret-postgres-password
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_DB=postgres
# jwt config
JWT_EXPIRY=3600
JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters
# both ANON_KEY and SERVICE_ROLE_KEY should be generated using the above set secret
# sign this payload -> {"role": "anon"} <- for ANON_KEY with JWT_SECRET
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiJ9.HmbvN-NgnsK1Lbk5e8Dti9SS6SFB384DsIT8QVLwUo8
# sign this payload -> {"role": "service_role"} <- for SERVICE_ROLE_KEY with JWT_SECRET
SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIn0.ym708-_0SlTjvc0pAK56bXQK2srKAo8RbJfA3czgkFo
KONG_HTTP_PORT=8000
# auth (gotrue)
API_EXTERNAL_URL=http://localhost:8000
SITE_URL=http://localhost:3000
ADDITIONAL_REDIRECT_URLS=http://localhost:3000/**,http://localhost:8000/**
# readest auth stuff
DISABLE_SIGNUP=false
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=true
ENABLE_ANONYMOUS_USERS=false
# only needed if ENABLE_EMAIL_AUTOCONFIRM is false
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_ADMIN_EMAIL=admin@example.com
SMTP_SENDER_NAME=Readest
# PostgREST shiz
PGRST_DB_SCHEMAS=public,graphql_public
# s3 storage through MinIO
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin-secret-password
S3_BUCKET_NAME=readest-files
# 1GB fixed quota for storage and 50k for translations
STORAGE_FIXED_QUOTA=1073741824
TRANSLATION_FIXED_QUOTA=50000

122
docker/README.md Normal file
View file

@ -0,0 +1,122 @@
# Self-Hosting with Docker/Podman with Compose
## Stack
| service | Image | Description |
| --------------- | --------------------------- | ------------------------------------------------- |
| **client** | from `../Dockerfile` | readest frontend |
| **db** | `supabase/postgres` | psql db with supabase extensions |
| **kong** | `kong:2.8.1` | api gateway routing requests to supabase services |
| **auth** | `supabase/gotrue:v2.185.0` | auth service (email, JWT) |
| **rest** | `postgrest/postgrest:v14.3` | psql rest api |
| **minio** | `minio/minio` | s3 storage |
| **minio-setup** | `minio/mc` | helper container to create s3 buckets |
### Exposed ports
| Port | Service |
| ------ | ---------------- |
| `3000` | readest |
| `7000` | kong API gateway |
| `9000` | MinIO S3 API |
| `9001` | MinIO console UI |
---
## Running with Docker/Podman Compose
### 1. setup .env
```bash
cp docker/.env.example docker/.env
```
update `docker/.env`:
- update `POSTGRES_PASSWORD` to a strong password (32+ chars)
- update `JWT_SECRET` to a random secret (32+ chars)
- regenerate `ANON_KEY` and `SERVICE_ROLE_KEY` as HS256 JWTs signed with your `JWT_SECRET` (use [jwt.io](https://jwt.io/) or a similar tool):
- `ANON_KEY` payload: `{"role": "anon"}`
- `SERVICE_ROLE_KEY` payload: `{"role": "service_role"}`
- set `MINIO_ROOT_PASSWORD` to a strong password
### 2. Start the Stack
run from the `docker/` directory:
```bash
cd docker
docker compose up --build -d
```
the client image is built locally on first run. subsequent starts reuse the cached image.
### 3. Access
- Readest app: `http://localhost:3000`
- MinIO console: `http://localhost:9001` (login with `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD`)
### Hot Reload (development)
to develop using the compose stack, set the build target on `client` to `development-stage`, which'll runs the next.js dev server. to enable hot reload, uncomment the `volumes` block in the `client` service in `compose.yaml`:
```yaml
volumes:
- ../:/app
- /app/node_modules
- /app/apps/readest-app/node_modules
- /app/apps/readest-app/public/vendor
- /app/apps/readest-app/.next
- /app/packages/foliate-js/node_modules
```
the first mount overlays your local repo into the container. the remaining anonymous volumes shadow the directories that were pre-built inside the image, so the container's installed deps and vendor assets are used instead of what's on your host.
### Stop the Stack
```bash
cd docker
docker compose down
```
to also remove volumes (database and storage data):
```bash
cd docker
docker compose down -v
```
---
## Building the Dockerfile standalone
the `Dockerfile` requires Build args for the next.js public env vars (they are inlined at build time)
```bash
docker build \
--target production-stage \
--build-arg NEXT_PUBLIC_SUPABASE_URL=http://localhost:7000 \
--build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key> \
--build-arg NEXT_PUBLIC_APP_PLATFORM=web \
--build-arg NEXT_PUBLIC_API_BASE_URL=http://localhost:3000 \
--build-arg NEXT_PUBLIC_OBJECT_STORAGE_TYPE=s3 \
--build-arg NEXT_PUBLIC_STORAGE_FIXED_QUOTA=1073741824 \
--build-arg NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA=50000 \
-t readest-client \
.
```
run the built image:
```bash
docker run -p 3000:3000 \
-e SUPABASE_URL=http://kong:8000 \
-e SUPABASE_ANON_KEY=<anon-key> \
-e SUPABASE_ADMIN_KEY=<service-role-key> \
-e S3_ENDPOINT=http://localhost:9000 \
-e S3_REGION=us-east-1 \
-e S3_BUCKET_NAME=readest-files \
-e S3_ACCESS_KEY_ID=<minio-user> \
-e S3_SECRET_ACCESS_KEY=<minio-password> \
readest-client
```

160
docker/compose.yaml Normal file
View file

@ -0,0 +1,160 @@
name: readest
services:
db:
container_name: supabase-db
image: supabase/postgres:15.8.1.085
restart: unless-stopped
volumes:
- ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
- ./volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z
- ./volumes/db/init/schema.sql:/docker-entrypoint-initdb.d/init-scripts/100-schema.sql:Z
- db-data:/var/lib/postgresql/data:Z
- db-config:/etc/postgresql-custom
environment:
POSTGRES_HOST: /var/run/postgresql
PGPORT: ${POSTGRES_PORT}
POSTGRES_PORT: ${POSTGRES_PORT}
PGPASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
PGDATABASE: ${POSTGRES_DB}
POSTGRES_DB: ${POSTGRES_DB}
JWT_SECRET: ${JWT_SECRET}
JWT_EXP: ${JWT_EXPIRY}
command:
- postgres
- -c
- config_file=/etc/postgresql/postgresql.conf
- -c
- log_min_messages=fatal
kong:
container_name: supabase-kong
image: kong:2.8.1
restart: unless-stopped
ports:
- "${KONG_HTTP_PORT}:8000/tcp"
volumes:
- ./volumes/api/kong.yml:/home/kong/temp.yml:ro,z
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
KONG_DNS_ORDER: LAST,A,CNAME
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
auth:
container_name: supabase-auth
image: supabase/gotrue:v2.185.0
restart: unless-stopped
environment:
GOTRUE_API_HOST: 0.0.0.0
GOTRUE_API_PORT: 9999
API_EXTERNAL_URL: ${API_EXTERNAL_URL}
GOTRUE_DB_DRIVER: postgres
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/${POSTGRES_DB}
GOTRUE_SITE_URL: ${SITE_URL}
GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS}
GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}
GOTRUE_JWT_ADMIN_ROLES: service_role
GOTRUE_JWT_AUD: authenticated
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
GOTRUE_JWT_EXP: ${JWT_EXPIRY}
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${ENABLE_ANONYMOUS_USERS}
GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
GOTRUE_SMTP_HOST: ${SMTP_HOST}
GOTRUE_SMTP_PORT: ${SMTP_PORT}
GOTRUE_SMTP_USER: ${SMTP_USER}
GOTRUE_SMTP_PASS: ${SMTP_PASS}
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
rest:
container_name: supabase-rest
image: postgrest/postgrest:v14.3
restart: unless-stopped
environment:
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/${POSTGRES_DB}
PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
PGRST_DB_USE_LEGACY_GUCS: "false"
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY}
command: ["postgrest"]
minio:
container_name: readest-minio
image: minio/minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
command: server --console-address ":9001" /data
volumes:
- minio-data:/data:z
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 5
minio-setup:
image: minio/mc
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set myminio http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD} &&
mc mb --ignore-existing myminio/${S3_BUCKET_NAME}
"
client:
container_name: readest-client
build:
context: ..
target: production-stage
args:
NEXT_PUBLIC_SUPABASE_URL: http://${HOST_IP}:7000
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
NEXT_PUBLIC_APP_PLATFORM: web
NEXT_PUBLIC_API_BASE_URL: http://${HOST_IP}:3000
NEXT_PUBLIC_OBJECT_STORAGE_TYPE: s3
NEXT_PUBLIC_STORAGE_FIXED_QUOTA: ${STORAGE_FIXED_QUOTA}
NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA: ${TRANSLATION_FIXED_QUOTA}
restart: unless-stopped
ports:
- "3000:3000"
# to enable hot reload, mount the source code as a volume, and artifacts as anon volumes
# volumes:
# - ../:/app
# - /app/node_modules
# - /app/apps/readest-app/node_modules
# - /app/apps/readest-app/public/vendor
# - /app/apps/readest-app/.next
# - /app/packages/foliate-js/node_modules
environment:
SUPABASE_URL: http://kong:8000
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_ADMIN_KEY: ${SERVICE_ROLE_KEY}
S3_ENDPOINT: http://${HOST_IP}:9000
S3_REGION: us-east-1
S3_BUCKET_NAME: ${S3_BUCKET_NAME}
S3_ACCESS_KEY_ID: ${MINIO_ROOT_USER}
S3_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD}
depends_on:
- kong
- minio
volumes:
db-config:
db-data:
minio-data:

View file

@ -0,0 +1,95 @@
_format_version: '2.1'
_transform: true
consumers:
- username: anon
keyauth_credentials:
- key: $SUPABASE_ANON_KEY
- username: service_role
keyauth_credentials:
- key: $SUPABASE_SERVICE_KEY
acls:
- consumer: anon
group: anon
- consumer: service_role
group: admin
services:
- name: auth-v1-health
url: http://auth:9999/health
routes:
- name: auth-v1-health
strip_path: true
paths:
- /auth/v1/health
plugins:
- name: cors
- name: auth-v1-open
url: http://auth:9999/verify
routes:
- name: auth-v1-open
strip_path: true
paths:
- /auth/v1/verify
plugins:
- name: cors
- name: auth-v1-open-callback
url: http://auth:9999/callback
routes:
- name: auth-v1-open-callback
strip_path: true
paths:
- /auth/v1/callback
plugins:
- name: cors
- name: auth-v1-open-authorize
url: http://auth:9999/authorize
routes:
- name: auth-v1-open-authorize
strip_path: true
paths:
- /auth/v1/authorize
plugins:
- name: cors
- name: auth-v1
url: http://auth:9999/
routes:
- name: auth-v1-all
strip_path: true
paths:
- /auth/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: rest-v1
url: http://rest:3000/
routes:
- name: rest-v1-all
strip_path: true
paths:
- /rest/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon

View file

@ -0,0 +1,105 @@
ALTER FUNCTION auth.uid() OWNER TO supabase_auth_admin;
ALTER FUNCTION auth.role() OWNER TO supabase_auth_admin;
CREATE TABLE public.books (
user_id uuid NOT NULL,
book_hash text NOT NULL,
meta_hash text NULL,
format text NULL,
title text NULL,
source_title text NULL,
author text NULL,
"group" text NULL,
tags text[] NULL,
created_at timestamp with time zone NULL DEFAULT now(),
updated_at timestamp with time zone NULL DEFAULT now(),
deleted_at timestamp with time zone NULL,
uploaded_at timestamp with time zone NULL,
progress integer[] NULL,
reading_status text NULL,
group_id text NULL,
group_name text NULL,
metadata json NULL,
CONSTRAINT books_pkey PRIMARY KEY (user_id, book_hash),
CONSTRAINT books_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
);
ALTER TABLE public.books ENABLE ROW LEVEL SECURITY;
CREATE POLICY select_books ON public.books FOR SELECT TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE POLICY insert_books ON public.books FOR INSERT TO authenticated WITH CHECK ((SELECT auth.uid()) = user_id);
CREATE POLICY update_books ON public.books FOR UPDATE TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE POLICY delete_books ON public.books FOR DELETE TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE TABLE public.book_configs (
user_id uuid NOT NULL,
book_hash text NOT NULL,
meta_hash text NULL,
location text NULL,
xpointer text NULL,
progress jsonb NULL,
search_config jsonb NULL,
view_settings jsonb NULL,
created_at timestamp with time zone NULL DEFAULT now(),
updated_at timestamp with time zone NULL DEFAULT now(),
deleted_at timestamp with time zone NULL,
CONSTRAINT book_configs_pkey PRIMARY KEY (user_id, book_hash),
CONSTRAINT book_configs_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
);
ALTER TABLE public.book_configs ENABLE ROW LEVEL SECURITY;
CREATE POLICY select_book_configs ON public.book_configs FOR SELECT TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE POLICY insert_book_configs ON public.book_configs FOR INSERT TO authenticated WITH CHECK ((SELECT auth.uid()) = user_id);
CREATE POLICY update_book_configs ON public.book_configs FOR UPDATE TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE POLICY delete_book_configs ON public.book_configs FOR DELETE TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE TABLE public.book_notes (
user_id uuid NOT NULL,
book_hash text NOT NULL,
meta_hash text NULL,
id text NOT NULL,
type text NULL,
cfi text NULL,
text text NULL,
style text NULL,
color text NULL,
note text NULL,
created_at timestamp with time zone NULL DEFAULT now(),
updated_at timestamp with time zone NULL DEFAULT now(),
deleted_at timestamp with time zone NULL,
CONSTRAINT book_notes_pkey PRIMARY KEY (user_id, book_hash, id),
CONSTRAINT book_notes_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
);
ALTER TABLE public.book_notes ENABLE ROW LEVEL SECURITY;
CREATE POLICY select_book_notes ON public.book_notes FOR SELECT TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE POLICY insert_book_notes ON public.book_notes FOR INSERT TO authenticated WITH CHECK ((SELECT auth.uid()) = user_id);
CREATE POLICY update_book_notes ON public.book_notes FOR UPDATE TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE POLICY delete_book_notes ON public.book_notes FOR DELETE TO authenticated USING ((SELECT auth.uid()) = user_id);
CREATE TABLE public.files (
id uuid NOT NULL DEFAULT gen_random_uuid(),
user_id uuid NOT NULL,
book_hash text NULL,
file_key text NOT NULL,
file_size bigint NOT NULL,
created_at timestamp with time zone NULL DEFAULT now(),
deleted_at timestamp with time zone NULL,
CONSTRAINT files_pkey PRIMARY KEY (id),
CONSTRAINT files_file_key_key UNIQUE (file_key),
CONSTRAINT files_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE
);
CREATE INDEX idx_files_user_id_deleted_at ON public.files (user_id, deleted_at);
CREATE INDEX idx_files_file_key ON public.files (file_key);
CREATE INDEX idx_files_file_key_deleted_at ON public.files (file_key, deleted_at);
ALTER TABLE public.files ENABLE ROW LEVEL SECURITY;
CREATE POLICY files_insert ON public.files FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY files_select ON public.files FOR SELECT USING (auth.uid() = user_id AND deleted_at IS NULL);
CREATE POLICY files_update ON public.files FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (deleted_at IS NULL OR deleted_at > now());
CREATE POLICY files_delete ON public.files FOR DELETE USING (auth.uid() = user_id);
GRANT ALL ON public.books TO authenticated;
GRANT ALL ON public.book_configs TO authenticated;
GRANT ALL ON public.book_notes TO authenticated;
GRANT ALL ON public.files TO authenticated;

View file

@ -0,0 +1,5 @@
\set jwt_secret `echo "$JWT_SECRET"`
\set jwt_exp `echo "$JWT_EXP"`
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';

View file

@ -0,0 +1,7 @@
-- NOTE: change to your own passwords for production environments
\set pgpass `echo "$POSTGRES_PASSWORD"`
ALTER USER authenticator WITH PASSWORD :'pgpass';
ALTER USER pgbouncer WITH PASSWORD :'pgpass';
ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';