Merge branch 'master' into develop

This commit is contained in:
Yann Autissier
2025-08-08 15:38:43 +02:00
69 changed files with 3184 additions and 95 deletions
+5
View File
@@ -0,0 +1,5 @@
DOCKER_MACHINE=${DOCKER_MACHINE}
DOCKER_SOCKET_LOCATION=${DOCKER_SOCKET_LOCATION}
DOCKER_SYSTEM=${DOCKER_SYSTEM}
DOMAIN=${DOMAIN}
STACK=${STACK}
+10
View File
@@ -0,0 +1,10 @@
ARG SUPABASE_EDGE_RUNTIME_VERSION=v1.65.3
FROM supabase/edge-runtime:${SUPABASE_EDGE_RUNTIME_VERSION}
ARG DOCKER_BUILD_DIR=.
RUN mkdir -p /home/deno/functions
COPY ${DOCKER_BUILD_DIR}/hello/ /home/deno/functions/hello/
COPY ${DOCKER_BUILD_DIR}/main/ /home/deno/functions/main/
VOLUME /home/deno/functions
VOLUME /root/.cache
@@ -0,0 +1,16 @@
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.177.1/http/server.ts"
serve(async () => {
return new Response(
`"Hello from Edge Functions!"`,
{ headers: { "Content-Type": "application/json" } },
)
})
// To invoke:
// curl 'http://localhost:<KONG_HTTP_PORT>/functions/v1/hello' \
// --header 'Authorization: Bearer <anon/service_role API key>'
@@ -0,0 +1,94 @@
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
console.log('main function started')
const JWT_SECRET = Deno.env.get('JWT_SECRET')
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
function getAuthToken(req: Request) {
const authHeader = req.headers.get('authorization')
if (!authHeader) {
throw new Error('Missing authorization header')
}
const [bearer, token] = authHeader.split(' ')
if (bearer !== 'Bearer') {
throw new Error(`Auth header is not 'Bearer {token}'`)
}
return token
}
async function verifyJWT(jwt: string): Promise<boolean> {
const encoder = new TextEncoder()
const secretKey = encoder.encode(JWT_SECRET)
try {
await jose.jwtVerify(jwt, secretKey)
} catch (err) {
console.error(err)
return false
}
return true
}
serve(async (req: Request) => {
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
try {
const token = getAuthToken(req)
const isValidJWT = await verifyJWT(token)
if (!isValidJWT) {
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
} catch (e) {
console.error(e)
return new Response(JSON.stringify({ msg: e.toString() }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
}
const url = new URL(req.url)
const { pathname } = url
const path_parts = pathname.split('/')
const service_name = path_parts[1]
if (!service_name || service_name === '') {
const error = { msg: 'missing function name in request' }
return new Response(JSON.stringify(error), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const servicePath = `/home/deno/functions/${service_name}`
console.error(`serving the request with ${servicePath}`)
const memoryLimitMb = 150
const workerTimeoutMs = 1 * 60 * 1000
const noModuleCache = false
const importMapPath = null
const envVarsObj = Deno.env.toObject()
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
try {
const worker = await EdgeRuntime.userWorkers.create({
servicePath,
memoryLimitMb,
workerTimeoutMs,
noModuleCache,
importMapPath,
envVars,
})
return await worker.fetch(req)
} catch (e) {
const error = { msg: e.toString() }
return new Response(JSON.stringify(error), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
})
+12
View File
@@ -0,0 +1,12 @@
ARG SUPABASE_KONG_VERSION=3.9.0
FROM kong:${SUPABASE_KONG_VERSION}
ARG DOCKER_BUILD_DIR=.
ARG DOCKER_NETWORK=supabase_default
ARG REALTIME_TENANT=realtime-dev
RUN mkdir -p /home/kong
COPY ${DOCKER_BUILD_DIR}/kong.yml /home/kong/temp.yml
# realtime tenant is taken from hostname in url
RUN sed -i 's/realtime-dev.supabase-realtime/'"${REALTIME_TENANT}"'.'"${DOCKER_NETWORK}"'/' /home/kong/temp.yml
VOLUME /home/kong
+241
View File
@@ -0,0 +1,241 @@
_format_version: '2.1'
_transform: true
###
### Consumers / Users
###
consumers:
- username: DASHBOARD
- username: anon
keyauth_credentials:
- key: $SUPABASE_ANON_KEY
- username: service_role
keyauth_credentials:
- key: $SUPABASE_SERVICE_KEY
###
### Access Control List
###
acls:
- consumer: anon
group: anon
- consumer: service_role
group: admin
###
### Dashboard credentials
###
basicauth_credentials:
- consumer: DASHBOARD
username: $DASHBOARD_USERNAME
password: $DASHBOARD_PASSWORD
###
### API Routes
###
services:
## Open Auth routes
- 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
## Secure Auth routes
- name: auth-v1
_comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*'
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
## Secure REST routes
- name: rest-v1
_comment: 'PostgREST: /rest/v1/* -> http://rest:3000/*'
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
## Secure GraphQL routes
- name: graphql-v1
_comment: 'PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql'
url: http://rest:3000/rpc/graphql
routes:
- name: graphql-v1-all
strip_path: true
paths:
- /graphql/v1
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
- name: request-transformer
config:
add:
headers:
- Content-Profile:graphql_public
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Secure Realtime routes
- name: realtime-v1-ws
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
url: http://realtime-dev.supabase-realtime:4000/socket
protocol: ws
routes:
- name: realtime-v1-ws
strip_path: true
paths:
- /realtime/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: realtime-v1-rest
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
url: http://realtime-dev.supabase-realtime:4000/api
protocol: http
routes:
- name: realtime-v1-rest
strip_path: true
paths:
- /realtime/v1/api
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Storage routes: the storage server manages its own auth
- name: storage-v1
_comment: 'Storage: /storage/v1/* -> http://storage:5000/*'
url: http://storage:5000/
routes:
- name: storage-v1-all
strip_path: true
paths:
- /storage/v1/
plugins:
- name: cors
## Edge Functions routes
- name: functions-v1
_comment: 'Edge Functions: /functions/v1/* -> http://functions:9000/*'
url: http://functions:9000/
routes:
- name: functions-v1-all
strip_path: true
paths:
- /functions/v1/
plugins:
- name: cors
## Analytics routes
- name: analytics-v1
_comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*'
url: http://analytics:4000/
routes:
- name: analytics-v1-all
strip_path: true
paths:
- /analytics/v1/
## Secure Database routes
- name: meta
_comment: 'pg-meta: /pg/* -> http://pg-meta:8080/*'
url: http://meta:8080/
routes:
- name: meta-all
strip_path: true
paths:
- /pg/
plugins:
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
## Protected Dashboard - catch all remaining routes
- name: dashboard
_comment: 'Studio: /* -> http://studio:3000/*'
url: http://studio:3000/
routes:
- name: dashboard-all
strip_path: true
paths:
- /
plugins:
- name: cors
- name: basic-auth
config:
hide_credentials: true
+14
View File
@@ -0,0 +1,14 @@
ARG SUPABASE_POSTGRES_VERSION=15.6.1.146
FROM supabase/postgres:${SUPABASE_POSTGRES_VERSION}
ARG DOCKER_BUILD_DIR=.
COPY ${DOCKER_BUILD_DIR}/realtime.sql /docker-entrypoint-initdb.d/migrations/99-realtime.sql
COPY ${DOCKER_BUILD_DIR}/webhooks.sql /docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql
COPY ${DOCKER_BUILD_DIR}/roles.sql /docker-entrypoint-initdb.d/init-scripts/99-roles.sql
COPY ${DOCKER_BUILD_DIR}/jwt.sql /docker-entrypoint-initdb.d/init-scripts/99-jwt.sql
COPY ${DOCKER_BUILD_DIR}/_supabase.sql /docker-entrypoint-initdb.d/migrations/97-_supabase.sql
COPY ${DOCKER_BUILD_DIR}/logs.sql /docker-entrypoint-initdb.d/migrations/99-logs.sql
COPY ${DOCKER_BUILD_DIR}/pooler.sql /docker-entrypoint-initdb.d/migrations/99-pooler.sql
VOLUME /etc/postgresql-custom
VOLUME /var/lib/postgres
+3
View File
@@ -0,0 +1,3 @@
\set pguser `echo "$POSTGRES_USER"`
CREATE DATABASE _supabase WITH OWNER :pguser;
+5
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';
+6
View File
@@ -0,0 +1,6 @@
\set pguser `echo "$POSTGRES_USER"`
\c _supabase
create schema if not exists _analytics;
alter schema _analytics owner to :pguser;
\c postgres
+6
View File
@@ -0,0 +1,6 @@
\set pguser `echo "$POSTGRES_USER"`
\c _supabase
create schema if not exists _supavisor;
alter schema _supavisor owner to :pguser;
\c postgres
+4
View File
@@ -0,0 +1,4 @@
\set pguser `echo "$POSTGRES_USER"`
create schema if not exists _realtime;
alter schema _realtime owner to :pguser;
+8
View File
@@ -0,0 +1,8 @@
-- 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_functions_admin WITH PASSWORD :'pgpass';
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
+208
View File
@@ -0,0 +1,208 @@
BEGIN;
-- Create pg_net extension
CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
-- Create supabase_functions schema
CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
-- supabase_functions.migrations definition
CREATE TABLE supabase_functions.migrations (
version text PRIMARY KEY,
inserted_at timestamptz NOT NULL DEFAULT NOW()
);
-- Initial supabase_functions migration
INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
-- supabase_functions.hooks definition
CREATE TABLE supabase_functions.hooks (
id bigserial PRIMARY KEY,
hook_table_id integer NOT NULL,
hook_name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
request_id bigint
);
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
CREATE FUNCTION supabase_functions.http_request()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
request_id bigint;
payload jsonb;
url text := TG_ARGV[0]::text;
method text := TG_ARGV[1]::text;
headers jsonb DEFAULT '{}'::jsonb;
params jsonb DEFAULT '{}'::jsonb;
timeout_ms integer DEFAULT 1000;
BEGIN
IF url IS NULL OR url = 'null' THEN
RAISE EXCEPTION 'url argument is missing';
END IF;
IF method IS NULL OR method = 'null' THEN
RAISE EXCEPTION 'method argument is missing';
END IF;
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
headers = '{"Content-Type": "application/json"}'::jsonb;
ELSE
headers = TG_ARGV[2]::jsonb;
END IF;
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
params = '{}'::jsonb;
ELSE
params = TG_ARGV[3]::jsonb;
END IF;
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
timeout_ms = 1000;
ELSE
timeout_ms = TG_ARGV[4]::integer;
END IF;
CASE
WHEN method = 'GET' THEN
SELECT http_get INTO request_id FROM net.http_get(
url,
params,
headers,
timeout_ms
);
WHEN method = 'POST' THEN
payload = jsonb_build_object(
'old_record', OLD,
'record', NEW,
'type', TG_OP,
'table', TG_TABLE_NAME,
'schema', TG_TABLE_SCHEMA
);
SELECT http_post INTO request_id FROM net.http_post(
url,
payload,
params,
headers,
timeout_ms
);
ELSE
RAISE EXCEPTION 'method argument % is invalid', method;
END CASE;
INSERT INTO supabase_functions.hooks
(hook_table_id, hook_name, request_id)
VALUES
(TG_RELID, TG_NAME, request_id);
RETURN NEW;
END
$function$;
-- Supabase super admin
DO
$$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_roles
WHERE rolname = 'supabase_functions_admin'
)
THEN
CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
END IF;
END
$$;
GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
GRANT supabase_functions_admin TO postgres;
-- Remove unused supabase_pg_net_admin role
DO
$$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_roles
WHERE rolname = 'supabase_pg_net_admin'
)
THEN
REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
DROP OWNED BY supabase_pg_net_admin;
DROP ROLE supabase_pg_net_admin;
END IF;
END
$$;
-- pg_net grants when extension is already enabled
DO
$$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_extension
WHERE extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END
$$;
-- Event trigger for pg_net
CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_event_trigger_ddl_commands() AS ev
JOIN pg_extension AS ext
ON ev.objid = ext.oid
WHERE ext.extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END;
$$;
COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
DO
$$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_event_trigger
WHERE evtname = 'issue_pg_net_access'
) THEN
CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
EXECUTE PROCEDURE extensions.grant_pg_net_access();
END IF;
END
$$;
INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
ALTER function supabase_functions.http_request() SECURITY DEFINER;
ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
COMMIT;
+7
View File
@@ -0,0 +1,7 @@
ARG SUPABASE_VECTOR_VERSION=0.28.1-alpine
FROM timberio/vector:${SUPABASE_VECTOR_VERSION}
ARG DOCKER_BUILD_DIR=.
COPY ${DOCKER_BUILD_DIR}/vector.yml /etc/vector/vector.yml
VOLUME /etc/vector
+232
View File
@@ -0,0 +1,232 @@
api:
enabled: true
address: 0.0.0.0:9001
sources:
docker_host:
type: docker_logs
exclude_containers:
- supabase-vector
transforms:
project_logs:
type: remap
inputs:
- docker_host
source: |-
.project = "default"
.event_message = del(.message)
.appname = del(.container_name)
del(.container_created_at)
del(.container_id)
del(.source_type)
del(.stream)
del(.label)
del(.image)
del(.host)
del(.stream)
router:
type: route
inputs:
- project_logs
route:
kong: '.appname == "supabase-kong"'
auth: '.appname == "supabase-auth"'
rest: '.appname == "supabase-rest"'
realtime: '.appname == "supabase-realtime"'
storage: '.appname == "supabase-storage"'
functions: '.appname == "supabase-functions"'
db: '.appname == "supabase-db"'
# Ignores non nginx errors since they are related with kong booting up
kong_logs:
type: remap
inputs:
- router.kong
source: |-
req, err = parse_nginx_log(.event_message, "combined")
if err == null {
.timestamp = req.timestamp
.metadata.request.headers.referer = req.referer
.metadata.request.headers.user_agent = req.agent
.metadata.request.headers.cf_connecting_ip = req.client
.metadata.request.method = req.method
.metadata.request.path = req.path
.metadata.request.protocol = req.protocol
.metadata.response.status_code = req.status
}
if err != null {
abort
}
# Ignores non nginx errors since they are related with kong booting up
kong_err:
type: remap
inputs:
- router.kong
source: |-
.metadata.request.method = "GET"
.metadata.response.status_code = 200
parsed, err = parse_nginx_log(.event_message, "error")
if err == null {
.timestamp = parsed.timestamp
.severity = parsed.severity
.metadata.request.host = parsed.host
.metadata.request.headers.cf_connecting_ip = parsed.client
url, err = split(parsed.request, " ")
if err == null {
.metadata.request.method = url[0]
.metadata.request.path = url[1]
.metadata.request.protocol = url[2]
}
}
if err != null {
abort
}
# Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.
auth_logs:
type: remap
inputs:
- router.auth
source: |-
parsed, err = parse_json(.event_message)
if err == null {
.metadata.timestamp = parsed.time
.metadata = merge!(.metadata, parsed)
}
# PostgREST logs are structured so we separate timestamp from message using regex
rest_logs:
type: remap
inputs:
- router.rest
source: |-
parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.timestamp = to_timestamp!(parsed.time)
.metadata.host = .project
}
# Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date)
realtime_logs:
type: remap
inputs:
- router.realtime
source: |-
.metadata.project = del(.project)
.metadata.external_id = .metadata.project
parsed, err = parse_regex(.event_message, r'^(?P<time>\d+:\d+:\d+\.\d+) \[(?P<level>\w+)\] (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
}
# Storage logs may contain json objects so we parse them for completeness
storage_logs:
type: remap
inputs:
- router.storage
source: |-
.metadata.project = del(.project)
.metadata.tenantId = .metadata.project
parsed, err = parse_json(.event_message)
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
.metadata.timestamp = parsed.time
.metadata.context[0].host = parsed.hostname
.metadata.context[0].pid = parsed.pid
}
# Postgres logs some messages to stderr which we map to warning severity level
db_logs:
type: remap
inputs:
- router.db
source: |-
.metadata.host = "db-default"
.metadata.parsed.timestamp = .timestamp
parsed, err = parse_regex(.event_message, r'.*(?P<level>INFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)
if err != null || parsed == null {
.metadata.parsed.error_severity = "info"
}
if parsed != null {
.metadata.parsed.error_severity = parsed.level
}
if .metadata.parsed.error_severity == "info" {
.metadata.parsed.error_severity = "log"
}
.metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)
sinks:
logflare_auth:
type: 'http'
inputs:
- auth_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=gotrue.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_realtime:
type: 'http'
inputs:
- realtime_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=realtime.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_rest:
type: 'http'
inputs:
- rest_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=postgREST.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_db:
type: 'http'
inputs:
- db_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
# We must route the sink through kong because ingesting logs before logflare is fully initialised will
# lead to broken queries from studio. This works by the assumption that containers are started in the
# following order: vector > db > logflare > kong
uri: 'http://kong:8000/analytics/v1/api/logs?source_name=postgres.logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_functions:
type: 'http'
inputs:
- router.functions
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=deno-relay-logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_storage:
type: 'http'
inputs:
- storage_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=storage.logs.prod.2&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_kong:
type: 'http'
inputs:
- kong_logs
- kong_err
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=cloudflare.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+4 -2
View File
@@ -5,7 +5,8 @@
# on local host # on local host
.PHONY: build-env .PHONY: build-env
build-env: SERVICE ?= $(DOCKER_SERVICE) build-env: SERVICE ?= $(DOCKER_SERVICE)
build-env: bootstrap build-env: APP_NAME := $(subst _,,$(subst -,,$(subst .,,$(call LOWERCASE,$(firstword $(subst /, ,$(STACK)))))))
build-env: bootstrap stack
$(call docker-compose-exec-sh,$(SERVICE), \ $(call docker-compose-exec-sh,$(SERVICE), \
rm -f .env \ rm -f .env \
&& make .env ENV=$(ENV) \ && make .env ENV=$(ENV) \
@@ -25,7 +26,8 @@ build-init:
# on local host # on local host
.PHONY: build-shared .PHONY: build-shared
build-shared: SERVICE ?= $(DOCKER_SERVICE) build-shared: SERVICE ?= $(DOCKER_SERVICE)
build-shared: bootstrap build-shared: APP_NAME := $(subst _,,$(subst -,,$(subst .,,$(call LOWERCASE,$(firstword $(subst /, ,$(STACK)))))))
build-shared: bootstrap stack
$(call docker-compose-exec-sh,$(SERVICE), \ $(call docker-compose-exec-sh,$(SERVICE), \
mkdir -p /$(notdir $(SHARED)) \ mkdir -p /$(notdir $(SHARED)) \
&& $(foreach folder,$(SHARED_FOLDERS), \ && $(foreach folder,$(SHARED_FOLDERS), \
+25 -20
View File
@@ -32,7 +32,7 @@ bootstrap-stack: docker-network debug-STACK $(foreach stack,$(STACK),bootstrap-s
# target build: Build application docker images to run # target build: Build application docker images to run
# on local host # on local host
.PHONY: build .PHONY: build
build: docker-compose-build ## Build application docker images build: docker-stack-build ## Build application docker images
# target build@%: Build application docker images of % ENV # target build@%: Build application docker images of % ENV
# on local host # on local host
@@ -50,22 +50,24 @@ build@%: myos-user
# target clean: Clean application and docker images # target clean: Clean application and docker images
# on local host # on local host
.PHONY: clean app-clean .PHONY: clean app-clean
clean: app-clean docker-rm docker-images-rm docker-volume-rm .env-clean ## Clean application and docker stuffs clean: DOCKER_COMPOSE_DOWN_OPTIONS += --rmi all --volumes
clean: app-clean docker-stack-down $(foreach stack,$(STACK),docker-image-rm-$(STACK)) .env-clean ## Clean application and docker stuffs
# target clean@%: Clean deployed application and docker images of % ENV # target clean@%: Clean deployed application and docker images of % ENV
# on local host # on local host
.PHONY: clean@% .PHONY: clean@%
clean@%: docker-rm docker-image-rm docker-volume-rm; clean@%: DOCKER_COMPOSE_DOWN_OPTIONS += --rmi all --volumes
clean@%: docker-stack-down;
# target config: View application docker compose file # target config: View application docker compose file
# on local host # on local host
.PHONY: config .PHONY: config
config: docker-compose-config ## View application docker compose file config: docker-stack-config ## View application docker compose file
# target connect: Connect to docker SERVICE # target connect: Connect to docker SERVICE
# on local host # on local host
.PHONY: connect .PHONY: connect
connect: docker-compose-connect ## Connect to docker SERVICE connect: docker-stack-connect ## Connect to docker SERVICE
# target connect@%: Connect to docker SERVICE of % ENV # target connect@%: Connect to docker SERVICE of % ENV
# on first remote host # on first remote host
@@ -81,13 +83,14 @@ deploy: $(if $(filter $(ENV),$(ENV_DEPLOY)),deploy-localhost,deploy@$(ENV)) ## D
# target down: Remove application dockers # target down: Remove application dockers
# on local host # on local host
.PHONY: down .PHONY: down
down: docker-compose-down ufw-delete ## Remove application dockers down: docker-stack-down ufw-delete ## Remove application dockers
# target exec: Exec ARGS in docker SERVICE # target exec: Exec ARGS in docker SERVICE
# on local host # on local host
.PHONY: exec .PHONY: exec
exec: SERVICE ?= $(DOCKER_SERVICE) exec: SERVICE ?= $(DOCKER_SERVICE)
exec: ## Exec command in docker SERVICE exec: APP_NAME := $(subst _,,$(subst -,,$(subst .,,$(call LOWERCASE,$(firstword $(subst /, ,$(STACK)))))))
exec: stack ## Exec command in docker SERVICE
#ifneq (,$(filter $(ENV),$(ENV_DEPLOY))) #ifneq (,$(filter $(ENV),$(ENV_DEPLOY)))
# $(RUN) $(call exec,$(ARGS)) # $(RUN) $(call exec,$(ARGS))
#else #else
@@ -98,7 +101,7 @@ exec: ## Exec command in docker SERVICE
# on all remote hosts # on all remote hosts
.PHONY: exec@% .PHONY: exec@%
exec@%: SERVICE ?= $(DOCKER_SERVICE) exec@%: SERVICE ?= $(DOCKER_SERVICE)
exec@%: exec@%: stack
$(call make,ssh-exec,$(MYOS),APP ARGS SERVICE) $(call make,ssh-exec,$(MYOS),APP ARGS SERVICE)
# target force-%: Fire targets %, stack-user-% and stack-host-% # target force-%: Fire targets %, stack-user-% and stack-host-%
@@ -114,17 +117,17 @@ install: bootstrap app-install ## Install application
# target logs: Display application dockers logs # target logs: Display application dockers logs
# on local host # on local host
.PHONY: logs .PHONY: logs
logs: docker-compose-logs ## Display application dockers logs logs: docker-stack-logs ## Display application dockers logs
# target ps: List application dockers # target ps: List application dockers
# on local host # on local host
.PHONY: ps .PHONY: ps
ps: docker-compose-ps ## List application dockers ps: docker-stack-ps ## List application dockers
# target rebuild: Rebuild application docker images # target rebuild: Rebuild application docker images
# on local host # on local host
.PHONY: rebuild .PHONY: rebuild
rebuild: docker-compose-rebuild ## Rebuild application dockers images rebuild: docker-stack-rebuild ## Rebuild application dockers images
# target rebuild@%: Rebuild application docker images # target rebuild@%: Rebuild application docker images
# on local host # on local host
@@ -135,7 +138,7 @@ rebuild@%:
# target recreate: Recreate application dockers # target recreate: Recreate application dockers
# on local host # on local host
.PHONY: recreate .PHONY: recreate
recreate: docker-compose-recreate app-start ## Recreate application dockers recreate: docker-stack-recreate app-start ## Recreate application dockers
# target reinstall: Fire clean, Call .env target, Call install target # target reinstall: Fire clean, Call .env target, Call install target
# on local host # on local host
@@ -151,13 +154,14 @@ release: release-create ## Create release VERSION
# target restart: Restart application dockers # target restart: Restart application dockers
# on local host # on local host
.PHONY: restart .PHONY: restart
restart: docker-compose-restart app-start ## Restart application restart: docker-stack-restart app-start ## Restart application
# target run: Run command ARGS in a new docker SERVICE # target run: Run command ARGS in a new docker SERVICE
# on local host # on local host
.PHONY: run .PHONY: run
run: SERVICE ?= $(or $(DOCKER_COMPOSE_SERVICE),$(DOCKER_SERVICE)) run: SERVICE ?= $(or $(DOCKER_COMPOSE_SERVICE),$(DOCKER_SERVICE))
run: ## Run a command in a new docker run: APP_NAME := $(subst _,,$(subst -,,$(subst .,,$(call LOWERCASE,$(firstword $(subst /, ,$(STACK)))))))
run: stack ## Run a command in a new docker
#ifneq (,$(filter $(ENV),$(ENV_DEPLOY))) #ifneq (,$(filter $(ENV),$(ENV_DEPLOY)))
# $(call run,$(ARGS)) # $(call run,$(ARGS))
#else #else
@@ -175,7 +179,7 @@ run@%:
# target scale: Scale SERVICE application to NUM dockers # target scale: Scale SERVICE application to NUM dockers
# on local host # on local host
.PHONY: scale .PHONY: scale
scale: docker-compose-scale ## Scale SERVICE application to NUM dockers scale: docker-stack-scale ## Scale SERVICE application to NUM dockers
# target shutdown: remove application, host and user dockers # target shutdown: remove application, host and user dockers
# on local host # on local host
@@ -192,8 +196,9 @@ ssh@%:
## it updates COMPOSE_FILE with all .yml files of the current stack ## it updates COMPOSE_FILE with all .yml files of the current stack
.PHONY: stack .PHONY: stack
stack: stack:
$(call compose-file,docker) $(if $(filter-out . myos,$(MYOS)), \
$(foreach stackz,$(STACK),$(call docker-stack,$(stackz))) $(call compose-file,. docker), \
$(foreach stackz,$(STACK),$(call docker-stack,$(stackz))))
$(call compose-file,$(MYOS_STACK),$(MYOS_STACK_FILE)) $(call compose-file,$(MYOS_STACK),$(MYOS_STACK_FILE))
# target stack-%: Call docker-compose-% target on STACK # target stack-%: Call docker-compose-% target on STACK
@@ -211,12 +216,12 @@ stack-%:
# target start app-start: Start application dockers # target start app-start: Start application dockers
# on local host # on local host
.PHONY: start app-start .PHONY: start app-start
start: docker-compose-start ## Start application dockers start: docker-stack-start ## Start application dockers
# target stop: Stop application dockers # target stop: Stop application dockers
# on local host # on local host
.PHONY: stop .PHONY: stop
stop: docker-compose-stop ## Stop application dockers stop: docker-stack-stop ## Stop application dockers
# target tests app-tests: Test application # target tests app-tests: Test application
# on local host # on local host
@@ -226,7 +231,7 @@ tests: app-tests ## Test application
# target up: Create and start application dockers # target up: Create and start application dockers
# on local host # on local host
.PHONY: up .PHONY: up
up: docker-compose-up ufw-update app-start ## Create application dockers up: docker-stack-up ufw-update app-start ## Create application dockers
# target update app-update: Update application files # target update app-update: Update application files
# on local host # on local host
+11 -6
View File
@@ -1,6 +1,8 @@
COMPOSE_FILE_DEBUG ?= $(if $(DEBUG),true) COMPOSE_FILE_DEBUG ?= $(if $(DEBUG),true)
COMPOSE_FILE_DNS ?= false COMPOSE_FILE_DNS ?= false
COMPOSE_FILE_HOME ?= false COMPOSE_FILE_HOME ?= false
COMPOSE_FILE_LABELS ?= true
COMPOSE_FILE_NETWORKS ?= true
COMPOSE_FILE_NFS ?= $(MOUNT_NFS) COMPOSE_FILE_NFS ?= $(MOUNT_NFS)
COMPOSE_FILE_SSH ?= true COMPOSE_FILE_SSH ?= true
ifneq ($(SUBREPO),) ifneq ($(SUBREPO),)
@@ -8,14 +10,16 @@ COMPOSE_FILE_SUBREPO ?= true
else else
COMPOSE_FILE_APP ?= true COMPOSE_FILE_APP ?= true
endif endif
COMPOSE_FILE_SUFFIX ?= $(foreach suffix,$(call LOWERCASE,$(filter-out SUFFIX,$(patsubst COMPOSE_FILE_%,%,$(filter COMPOSE_FILE_%,$(MAKE_FILE_VARS))))),$(if $(filter-out false False FALSE,$(COMPOSE_FILE_$(call UPPERCASE,$(suffix)))),$(suffix))) COMPOSE_FILE_SUFFIX ?= $(foreach suffix,$(call LOWERCASE,$(filter-out SUFFIX,$(patsubst COMPOSE_FILE_%,%,$(filter COMPOSE_FILE_%,$(MAKE_CMD_VARS) $(MAKE_ENV_VARS) $(MAKE_FILE_VARS))))),$(if $(filter-out false False FALSE,$(COMPOSE_FILE_$(call UPPERCASE,$(suffix)))),$(suffix) $(foreach service,$(filter-out true True TRUE,$(COMPOSE_FILE_$(call UPPERCASE,$(suffix)))),$(suffix).$(service))))
COMPOSE_FILE_VOLUMES ?= true
COMPOSE_FILE_WWW ?= false COMPOSE_FILE_WWW ?= false
COMPOSE_IGNORE_ORPHANS ?= false COMPOSE_IGNORE_ORPHANS ?= false
COMPOSE_PROJECT_NAME ?= $(if $(DOCKER_COMPOSE_PROJECT_NAME),$(DOCKER_COMPOSE_PROJECT_NAME),$(subst .,,$(call LOWERCASE,$(USER)-$(APP_NAME)-$(ENV)$(addprefix -,$(subst /,,$(subst -,,$(APP_PATH))))))) COMPOSE_PROJECT_NAME ?= $(if $(DOCKER_COMPOSE_PROJECT_NAME),$(DOCKER_COMPOSE_PROJECT_NAME),$(subst .,,$(call LOWERCASE,$(USER)-$(APP_NAME)-$(ENV)$(addprefix -,$(subst /,,$(subst -,,$(APP_PATH)))))))
COMPOSE_SERVICE_NAME ?= $(if $(DOCKER_COMPOSE_SERVICE_NAME),$(DOCKER_COMPOSE_SERVICE_NAME),$(subst _,-,$(COMPOSE_PROJECT_NAME))) COMPOSE_SERVICE_NAME ?= $(if $(DOCKER_COMPOSE_SERVICE_NAME),$(DOCKER_COMPOSE_SERVICE_NAME),$(subst _,-,$(COMPOSE_PROJECT_NAME)))
COMPOSE_VERSION ?= 2.5.0 COMPOSE_VERSION ?= 2.24.4
CONTEXT += COMPOSE_FILE DOCKER_REPOSITORY CONTEXT += COMPOSE_FILE DOCKER_REPOSITORY
CONTEXT_DEBUG += DOCKER_BUILD_TARGET DOCKER_COMPOSE_PROJECT_NAME DOCKER_IMAGE_TAG DOCKER_REGISTRY DOCKER_SERVICE DOCKER_SERVICES CONTEXT_DEBUG += DOCKER_BUILD_TARGET DOCKER_COMPOSE_PROJECT_NAME DOCKER_IMAGE_TAG DOCKER_REGISTRY DOCKER_SERVICE DOCKER_SERVICES
DOCKER_ARGS ?= --log-level=$(LOG_LEVEL)
DOCKER_AUTHOR ?= $(DOCKER_AUTHOR_NAME) <$(DOCKER_AUTHOR_EMAIL)> DOCKER_AUTHOR ?= $(DOCKER_AUTHOR_NAME) <$(DOCKER_AUTHOR_EMAIL)>
DOCKER_AUTHOR_EMAIL ?= $(subst +git,+docker,$(GIT_AUTHOR_EMAIL)) DOCKER_AUTHOR_EMAIL ?= $(subst +git,+docker,$(GIT_AUTHOR_EMAIL))
DOCKER_AUTHOR_NAME ?= $(GIT_AUTHOR_NAME) DOCKER_AUTHOR_NAME ?= $(GIT_AUTHOR_NAME)
@@ -27,7 +31,7 @@ DOCKER_BUILD_TARGET ?= $(if $(filter $(ENV),$(DOCKER_BUILD_TARGETS))
DOCKER_BUILD_TARGET_DEFAULT ?= master DOCKER_BUILD_TARGET_DEFAULT ?= master
DOCKER_BUILD_TARGETS ?= $(ENV_DEPLOY) DOCKER_BUILD_TARGETS ?= $(ENV_DEPLOY)
DOCKER_BUILD_VARS ?= APP BRANCH COMPOSE_VERSION DOCKER_GID DOCKER_MACHINE DOCKER_REPOSITORY DOCKER_SYSTEM GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME SSH_REMOTE_HOSTS USER VERSION DOCKER_BUILD_VARS ?= APP BRANCH COMPOSE_VERSION DOCKER_GID DOCKER_MACHINE DOCKER_REPOSITORY DOCKER_SYSTEM GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME SSH_REMOTE_HOSTS USER VERSION
DOCKER_COMPOSE := $(or $(shell docker-compose --version 2>/dev/null |awk '$$4 != "v'"$(COMPOSE_VERSION)"'" {exit 1} END {if (NR == 0) exit 1}' && printf 'docker-compose\n'),$(shell docker compose >/dev/null 2>&1 && printf 'docker compose\n')) DOCKER_COMPOSE ?= $(or $(shell $(call verle,$(COMPOSE_VERSION),$(shell docker compose version --short 2>/dev/null)) && printf 'docker $(DOCKER_ARGS) compose\n'),$(shell $(call verle,$(COMPOSE_VERSION),$(shell docker-compose version --short 2>/dev/null)) && printf 'docker-compose\n'))
DOCKER_COMPOSE_ARGS ?= --ansi=auto DOCKER_COMPOSE_ARGS ?= --ansi=auto
DOCKER_COMPOSE_DOWN_OPTIONS ?= DOCKER_COMPOSE_DOWN_OPTIONS ?=
DOCKER_COMPOSE_FILE ?= docker-compose DOCKER_COMPOSE_FILE ?= docker-compose
@@ -38,7 +42,7 @@ DOCKER_COMPOSE_RUN_OPTIONS ?= --rm $(DOCKER_COMPOSE_RUN_ENTRYPOINT) $(DOCKE
DOCKER_COMPOSE_RUN_WORKDIR ?= $(if $(DOCKER_COMPOSE_WORKDIR),-w $(DOCKER_COMPOSE_WORKDIR)) DOCKER_COMPOSE_RUN_WORKDIR ?= $(if $(DOCKER_COMPOSE_WORKDIR),-w $(DOCKER_COMPOSE_WORKDIR))
DOCKER_COMPOSE_SERVICE_NAME ?= $(subst _,-,$(DOCKER_COMPOSE_PROJECT_NAME)) DOCKER_COMPOSE_SERVICE_NAME ?= $(subst _,-,$(DOCKER_COMPOSE_PROJECT_NAME))
DOCKER_COMPOSE_UP_OPTIONS ?= -d DOCKER_COMPOSE_UP_OPTIONS ?= -d
DOCKER_IMAGE_TAG ?= $(if $(filter true,$(DEPLOY)),$(if $(filter $(ENV),$(ENV_DEPLOY)),$(VERSION)),$(if $(DRONE_BUILD_NUMBER),$(DRONE_BUILD_NUMBER),latest)) DOCKER_IMAGE_TAG ?= $(if $(filter true,$(DEPLOY)),$(if $(filter $(ENV),$(ENV_DEPLOY)),$(VERSION)),$(if $(DRONE_BUILD_NUMBER),$(DRONE_BUILD_NUMBER),$(ENV)))
DOCKER_IMAGES ?= $(patsubst %/,%,$(patsubst docker/%,%,$(dir $(wildcard docker/*/Dockerfile)))) DOCKER_IMAGES ?= $(patsubst %/,%,$(patsubst docker/%,%,$(dir $(wildcard docker/*/Dockerfile))))
DOCKER_PLUGIN ?= rexray/s3fs:latest DOCKER_PLUGIN ?= rexray/s3fs:latest
DOCKER_PLUGIN_ARGS ?= $(foreach var,$(DOCKER_PLUGIN_VARS),$(if $(DOCKER_PLUGIN_$(var)),$(var)='$(DOCKER_PLUGIN_$(var))')) DOCKER_PLUGIN_ARGS ?= $(foreach var,$(DOCKER_PLUGIN_VARS),$(if $(DOCKER_PLUGIN_$(var)),$(var)='$(DOCKER_PLUGIN_$(var))'))
@@ -72,7 +76,7 @@ define compose-file
$(eval name := $(or $(2),$(DOCKER_COMPOSE_FILE))) $(eval name := $(or $(2),$(DOCKER_COMPOSE_FILE)))
$(eval suffix := $(or $(3),$(COMPOSE_FILE_SUFFIX))) $(eval suffix := $(or $(3),$(COMPOSE_FILE_SUFFIX)))
$(eval extension := $(or $(4),yml yaml)) $(eval extension := $(or $(4),yml yaml))
$(eval COMPOSE_FILE += $(wildcard $(foreach e,$(extension),$(foreach n,$(name),$(foreach p,$(path),$(p)/$(n).$(e) $(p)/$(n).$(ENV).$(e) $(foreach s,$(suffix),$(p)/$(n).$(s).$(e) $(p)/$(n).$(s).$(ENV).$(e))))))) $(eval COMPOSE_FILE += $(wildcard $(foreach e,$(extension),$(foreach n,$(name),$(foreach p,$(path),$(p)/$(n).$(e) $(p)/$(n).$(ENV).$(e) $(p)/$(ENV)/$(n).$(e) $(p)/$(ENV)/$(n).$(ENV).$(e) $(foreach s,$(suffix),$(p)/$(n).$(s).$(e) $(p)/$(n).$(s).$(ENV).$(e)))))))
endef endef
# function docker-build: Build docker image # function docker-build: Build docker image
define docker-build define docker-build
@@ -95,6 +99,7 @@ define docker-commit
$(RUN) docker commit $(container) $(repository):$(tag) $(RUN) docker commit $(container) $(repository):$(tag)
endef endef
# function docker-compose: Run docker-compose with arg 1 # function docker-compose: Run docker-compose with arg 1
# TODO: accept $2 COMPOSE_FILE and remove stack from prereq
define docker-compose define docker-compose
$(call INFO,docker-compose,$(1)) $(call INFO,docker-compose,$(1))
$(if $(COMPOSE_FILE), $(if $(COMPOSE_FILE),
@@ -148,7 +153,7 @@ define docker-stack-update
$(eval name := $(firstword $(subst :, ,$(stack)))) $(eval name := $(firstword $(subst :, ,$(stack))))
$(eval version := $(or $(2),$(if $(findstring :,$(stack)),$(lastword $(subst :, ,$(stack))),latest))) $(eval version := $(or $(2),$(if $(findstring :,$(stack)),$(lastword $(subst :, ,$(stack))),latest)))
$(eval path := $(patsubst %/,%,$(or $(3),$(if $(findstring /,$(1)),$(if $(wildcard stack/$(1) stack/$(1).yml),stack/$(if $(findstring .yml,$(1)),$(dir $(1)),$(if $(wildcard stack/$(1).yml),$(dir $(1)),$(1))),$(if $(wildcard stack/$(stackz)/$(1) stack/$(stackz)/$(1).yml),stack/$(stackz)/$(if $(findstring .yml,$(1)),$(dir $(1)),$(if $(wildcard stack/$(stackz)/$(1).yml),$(dir $(1)),$(1))),$(dir $(1))))),$(firstword $(wildcard stack/$(stackz)/$(name) stack/$(stackz) stack/$(name)))))) $(eval path := $(patsubst %/,%,$(or $(3),$(if $(findstring /,$(1)),$(if $(wildcard stack/$(1) stack/$(1).yml),stack/$(if $(findstring .yml,$(1)),$(dir $(1)),$(if $(wildcard stack/$(1).yml),$(dir $(1)),$(1))),$(if $(wildcard stack/$(stackz)/$(1) stack/$(stackz)/$(1).yml),stack/$(stackz)/$(if $(findstring .yml,$(1)),$(dir $(1)),$(if $(wildcard stack/$(stackz)/$(1).yml),$(dir $(1)),$(1))),$(dir $(1))))),$(firstword $(wildcard stack/$(stackz)/$(name) stack/$(stackz) stack/$(name))))))
$(call compose-file,$(path),$(name),$(COMPOSE_FILE_SUFFIX) $(version)) $(call compose-file,$(path),docker-compose $(name),$(COMPOSE_FILE_SUFFIX) $(version))
$(if $(wildcard $(path)/.env.dist),$(call .env,,$(path)/.env.dist,$(wildcard $(CONFIG)/$(ENV)/$(APP)/.env $(path)/.env.$(ENV) .env))) $(if $(wildcard $(path)/.env.dist),$(call .env,,$(path)/.env.dist,$(wildcard $(CONFIG)/$(ENV)/$(APP)/.env $(path)/.env.$(ENV) .env)))
endef endef
# function docker-tag: Tag docker image # function docker-tag: Tag docker image
+5 -5
View File
@@ -18,10 +18,9 @@ APP_REPOSITORY_SCHEME ?= $(shell printf '$(APP_REPOSITORY_URL)\n' |sed
APP_REPOSITORY_URI ?= $(shell printf '$(APP_REPOSITORY_URL)\n' |sed 's|.*://||;') APP_REPOSITORY_URI ?= $(shell printf '$(APP_REPOSITORY_URL)\n' |sed 's|.*://||;')
APP_REPOSITORY_URL ?= $(GIT_REPOSITORY) APP_REPOSITORY_URL ?= $(GIT_REPOSITORY)
APP_REQUIRED ?= $(APP_REPOSITORY) APP_REQUIRED ?= $(APP_REPOSITORY)
APP_SCHEME ?= https APP_SCHEME ?= http
APP_UPSTREAM_REPOSITORY ?= $(or $(shell git config --get remote.upstream.url 2>/dev/null),$(GIT_UPSTREAM_REPOSITORY)) APP_UPSTREAM_REPOSITORY ?= $(or $(shell git config --get remote.upstream.url 2>/dev/null),$(GIT_UPSTREAM_REPOSITORY))
APP_URI ?= $(patsubst %,%/$(APP_PATH),$(APP_HOST)) APP_URI ?= $(patsubst %,%/$(APP_PATH),$(APP_HOST))
APP_URIS ?= $(APP_URI)
APP_URL ?= $(patsubst %,$(APP_SCHEME)://%,$(APP_URI)) APP_URL ?= $(patsubst %,$(APP_SCHEME)://%,$(APP_URI))
APP_VERSION ?= $(VERSION) APP_VERSION ?= $(VERSION)
CONTEXT += APP APPS BRANCH DOMAIN VERSION RELEASE CONTEXT += APP APPS BRANCH DOMAIN VERSION RELEASE
@@ -37,10 +36,11 @@ SERVICES ?= $(DOCKER_SERVICES)
envprefix = $(foreach env,$(3),$(if $($(call UPPERCASE,$(1)_SERVICE_$(2)_$(env))),$(env)=$($(call UPPERCASE,$(1)_SERVICE_$(2)_$(env))))) envprefix = $(foreach env,$(3),$(if $($(call UPPERCASE,$(1)_SERVICE_$(2)_$(env))),$(env)=$($(call UPPERCASE,$(1)_SERVICE_$(2)_$(env)))))
patsublist = $(patsubst $(1),$(2),$(firstword $(3)))$(foreach pattern,$(wordlist 2,255,$(3)),$(comma)$(patsubst $(1),$(2),$(pattern))) patsublist = $(patsubst $(1),$(2),$(firstword $(3)))$(foreach pattern,$(wordlist 2,255,$(3)),$(comma)$(patsubst $(1),$(2),$(pattern)))
servicenvs = $(foreach env,$(call UPPERCASE,$($(1)_SERVICE_$(2)_ENVS)),$(if $(3),$($(1)_SERVICE_$(env)_$(3)),$($(1)_SERVICE_$(2)_$(env)))) servicenvs = $(foreach env,$(call UPPERCASE,$($(1)_SERVICE_$(2)_ENVS)),$(if $(3),$($(1)_SERVICE_$(env)_$(3)),$($(1)_SERVICE_$(2)_$(env))))
tagprefix = $(call urlprefix,$(or $($(call UPPERCASE,$(1)_SERVICE_$(2)_PATH)),$($(call UPPERCASE,$(1)_SERVICE_PATH))),$(or $($(call UPPERCASE,$(1)_SERVICE_$(2)_OPTS)),$($(call UPPERCASE,$(1)_SERVICE_OPTS)),$(call envprefix,$(1),$(2),allow auth deny preprend proto register strip)),$(or $(foreach env,$(3),$($(call UPPERCASE,$(1)_SERVICE_$(2)_$(env)))),$($(call UPPERCASE,$(1)_SERVICE_$(2)_URIS)),$(call uriprefix,$(1),$(2)))) tagprefix = $(call urlprefix,$(or $($(call UPPERCASE,$(1)_SERVICE_$(2)_PATH)),$($(call UPPERCASE,$(1)_SERVICE_PATH))),$(or $($(call UPPERCASE,$(1)_SERVICE_$(2)_OPTS)),$($(call UPPERCASE,$(1)_SERVICE_OPTS)),$(call envprefix,$(1),$(2),allow auth deny preprend proto register strip)),$(or $(foreach env,$(3),$($(call UPPERCASE,$(1)_SERVICE_$(2)_$(env)))),$($(call UPPERCASE,$(1)_SERVICE_$(2)_URIS)),$(call uri,$(1),$(2))))
uriprefix = $(foreach svc,$(1),$(patsubst %,$(addsuffix .,$(or $($(call UPPERCASE,$(svc)_SERVICE_$(2)_NAME)),$($(call UPPERCASE,$(svc)_SERVICE_NAME)),$(svc)))%,$(or $(3),$(APP_URIS)))) uri = $(foreach svc,$(1),$(patsubst %,$(addsuffix .,$(or $($(call UPPERCASE,$(svc)_SERVICE_$(2)_NAME)),$($(call UPPERCASE,$(svc)_SERVICE_NAME)),$(svc)))%,$(or $(3),$(APP_URI))))
url = $(patsubst %,$(APP_SCHEME)://%,$(call uri,$(1),$(2),$(or $(3),$(APP_URI))))
url_suffix = * url_suffix = *
urlprefix = $(strip $(call patsublist,%,urlprefix-%$(1)$(url_suffix) $(2),$(or $(3),$(APP_URIS)))) urlprefix = $(strip $(call patsublist,%,urlprefix-%$(1)$(url_suffix) $(2),$(or $(3),$(APP_URI))))
urlprefixs = $(strip $(call urlprefix,$(firstword $(1)),$(wordlist 2,16,$(1)))$(foreach prefix,$(subst $(space),$(dollar),$(2)) $(subst $(space),$(dollar),$(3)) $(subst $(space),$(dollar),$(4)),$(comma)$(call subst,$(dollar),$(space),$(call urlprefix,$(firstword $(prefix)),$(wordlist 2,16,$(prefix)))))) urlprefixs = $(strip $(call urlprefix,$(firstword $(1)),$(wordlist 2,16,$(1)))$(foreach prefix,$(subst $(space),$(dollar),$(2)) $(subst $(space),$(dollar),$(3)) $(subst $(space),$(dollar),$(4)),$(comma)$(call subst,$(dollar),$(space),$(call urlprefix,$(firstword $(prefix)),$(wordlist 2,16,$(prefix))))))
## urlprefix tests (x APP_URI) ## urlprefix tests (x APP_URI)
# $(call urlprefix) # $(call urlprefix)
-4
View File
@@ -1,8 +1,6 @@
MAKECMDARGS += ufw ufw-docker MAKECMDARGS += ufw ufw-docker
UFW_UPDATE ?= $(or $(SERVICE),$(DOCKER_SERVICES)) UFW_UPDATE ?= $(or $(SERVICE),$(DOCKER_SERVICES))
ifeq ($(SETUP_UFW),true)
# function ufw: Exec command ufw with args 1 # function ufw: Exec command ufw with args 1
define ufw define ufw
$(call INFO,ufw,$(1)$(comma)) $(call INFO,ufw,$(1)$(comma))
@@ -18,5 +16,3 @@ define ufw-docker
$(eval DOCKER_COMPOSE_PROJECT_NAME := $(HOST_COMPOSE_PROJECT_NAME)) $(eval DOCKER_COMPOSE_PROJECT_NAME := $(HOST_COMPOSE_PROJECT_NAME))
$(call app-exec,,$(if $(DOCKER_RUN),,$(SUDO)) ufw-docker $(1)) $(call app-exec,,$(if $(DOCKER_RUN),,$(SUDO)) ufw-docker $(1))
endef endef
endif
+32 -25
View File
@@ -1,9 +1,9 @@
## ##
# DOCKER # DOCKER
# target docker-build: Fire docker-images-myos, Call docker-build-% target for each DOCKER_IMAGES # target docker-build: Fire docker-image-myos, Call docker-build-% target for each DOCKER_IMAGES
.PHONY: docker-build .PHONY: docker-build
docker-build: docker-images-myos docker-build: docker-image-myos
$(foreach image,$(or $(SERVICE),$(DOCKER_IMAGES)),$(call make,docker-build-$(image))) $(foreach image,$(or $(SERVICE),$(DOCKER_IMAGES)),$(call make,docker-build-$(image)))
# target docker-build-%: Call docker-build for each Dockerfile in docker/% folder # target docker-build-%: Call docker-build for each Dockerfile in docker/% folder
@@ -23,10 +23,10 @@ docker-commit: stack
docker-commit-%: stack docker-commit-%: stack
$(foreach service,$(or $(SERVICE),$(SERVICES)),$(call docker-commit,$(service),,,$*)) $(foreach service,$(or $(SERVICE),$(SERVICES)),$(call docker-commit,$(service),,,$*))
# target docker-compose-build: Fire docker-images-myos, Call docker-compose build SERVICE # target docker-compose-build: Fire docker-image-myos, Call docker-compose build SERVICE
.PHONY: docker-compose-build .PHONY: docker-compose-build
docker-compose-build: DOCKER_RUN_OPTIONS += -it docker-compose-build: DOCKER_RUN_OPTIONS += -it
docker-compose-build: docker-images-myos stack docker-compose-build: docker-image-myos stack
$(call docker-compose,build $(DOCKER_BUILD_ARGS) $(if $(filter $(SERVICE),$(SERVICES)),$(SERVICE))) $(call docker-compose,build $(DOCKER_BUILD_ARGS) $(if $(filter $(SERVICE),$(SERVICES)),$(SERVICE)))
# target docker-compose-config: Call docker-compose config # target docker-compose-config: Call docker-compose config
@@ -109,37 +109,38 @@ docker-compose-stop: stack
# target docker-compose-up: Fire docker-image-myos, Call docker-compose up SERVICE # target docker-compose-up: Fire docker-image-myos, Call docker-compose up SERVICE
.PHONY: docker-compose-up .PHONY: docker-compose-up
docker-compose-up: DOCKER_RUN_OPTIONS += -it docker-compose-up: DOCKER_RUN_OPTIONS += -it
docker-compose-up: docker-images-myos bootstrap-stack stack docker-compose-up: docker-image-myos bootstrap-stack stack
$(call docker-compose,up $(DOCKER_COMPOSE_UP_OPTIONS) $(if $(filter $(SERVICE),$(SERVICES)),$(SERVICE))) $(call docker-compose,up $(DOCKER_COMPOSE_UP_OPTIONS) $(if $(filter $(SERVICE),$(SERVICES)),$(SERVICE)))
# target docker-images-myos: Call myos-docker-build-% target for each DOCKER_IMAGES_MYOS # target docker-image-myos: Call myos-docker-build-% target for each MYOS_DOCKER_IMAGES
.PHONY: docker-images-myos .PHONY: docker-image-myos
docker-images-myos: MAKE_VARS += DOCKER_REPOSITORY STACK docker-image-myos: MAKE_VARS += DOCKER_REPOSITORY STACK
docker-images-myos: docker-image-myos:
$(foreach image,$(subst $(quote),,$(DOCKER_IMAGES_MYOS)),$(call make,docker-build-$(image),$(MYOS))) $(foreach image,$(subst $(quote),,$(MYOS_DOCKER_IMAGES)),$(call make,docker-build-$(image),$(MYOS)))
# target docker-images-rm: Remove docker images matching DOCKER_REPOSITORY # target docker-image-rm: Remove docker images matching DOCKER_REPOSITORY
.PHONY: docker-images-rm .PHONY: docker-image-rm
docker-images-rm: docker-image-rm:
docker images |awk '$$1 ~ /^$(subst /,\/,$(DOCKER_REPOSITORY)/)/ {print $$3}' |sort -u |while read image; do $(RUN) docker rmi -f $$image; done docker image ls |awk '$$1 ~ /^$(subst /,\/,$(DOCKER_REPOSITORY)/)/ {print $$3}' |sort -u |while read image; do $(RUN) docker rmi -f $$image; done
# target docker-images-rm-%: Remove docker images matching % # target docker-image-rm-%: Remove docker images matching %
.PHONY: docker-images-rm-% .PHONY: docker-image-rm-%
docker-images-rm-%: docker-image-rm-%:
docker images |awk '$$1 ~ /^$(subst /,\/,$*)/ {print $$3}' |sort -u |while read image; do $(RUN) docker rmi -f $$image; done $(foreach image,$(or $($(call UPPERCASE,$*)_DOCKER_IMAGES),$*), \
docker image ls |awk '$$1 ~ /^$(subst /,\/,$(firstword $(subst :, ,$(image))))/ {print $$3}' |sort -u |while read image; do $(RUN) docker rmi -f $$image; done;)
# target docker-login: Run 'docker login' # target docker-login: Run 'docker login'
.PHONY: docker-login .PHONY: docker-login
docker-login: myos-user docker-login: myos-user
$(RUN) docker login $(RUN) docker login
# target docker-network: Fire docker-network-create-% for DOCKER_NETWORK and DOCKER_NETWORK_PUBLIC # target docker-network: Fire docker-network-create
.PHONY: docker-network .PHONY: docker-network
docker-network: docker-network-create docker-network-create-$(DOCKER_NETWORK_PUBLIC) docker-network: docker-network-create
# target docker-network-create: Fire docker-network-create-% for DOCKER_NETWORK # target docker-network-create: Fire docker-network-create-% for DOCKER_NETWORK_PRIVATE and DOCKER_NETWORK_PUBLIC
.PHONY: docker-network-create .PHONY: docker-network-create
docker-network-create: docker-network-create-$(DOCKER_NETWORK) docker-network-create: docker-network-create-$(DOCKER_NETWORK_PRIVATE) docker-network-create-$(DOCKER_NETWORK_PUBLIC)
# target docker-network-create-%: Run 'docker network create %' # target docker-network-create-%: Run 'docker network create %'
.PHONY: docker-network-create-% .PHONY: docker-network-create-%
@@ -150,9 +151,9 @@ docker-network-create-%:
||: ; \ ||: ; \
fi fi
# target docker-network-rm: Fire docker-network-rm-% for DOCKER_NETWORK # target docker-network-rm: Fire docker-network-rm-% for DOCKER_NETWORK_PRIVATE and DOCKER_NETWORK_PUBLIC
.PHONY: docker-network-rm .PHONY: docker-network-rm
docker-network-rm: docker-network-rm-$(DOCKER_NETWORK) docker-network-rm: docker-network-rm-$(DOCKER_NETWORK_PRIVATE) docker-network-rm-$(DOCKER_NETWORK_PUBLIC)
# target docker-network-rm-%: Run 'docker network rm %' # target docker-network-rm-%: Run 'docker network rm %'
.PHONY: docker-network-rm-% .PHONY: docker-network-rm-%
@@ -226,9 +227,15 @@ docker-run-%: docker-build-%
$(eval command := $(ARGS)) $(eval command := $(ARGS))
$(eval path := $(patsubst %/,%,$*)) $(eval path := $(patsubst %/,%,$*))
$(eval image := $(DOCKER_REPOSITORY)/$(lastword $(subst /, ,$(path)))$(if $(findstring :,$*),,:$(DOCKER_IMAGE_TAG))) $(eval image := $(DOCKER_REPOSITORY)/$(lastword $(subst /, ,$(path)))$(if $(findstring :,$*),,:$(DOCKER_IMAGE_TAG)))
$(eval image_id := $(shell docker images -q $(image) 2>/dev/null)) $(eval image_id := $(shell docker image ls -q $(image) 2>/dev/null))
$(call docker-run,$(if $(image_id),$(image),$(path)),$(command)) $(call docker-run,$(if $(image_id),$(image),$(path)),$(command))
# target docker-stack-%: Call docker-compose-% target for each STACK
.PHONY: docker-stack-%
docker-stack-%: MAKE_VARS += DOCKER_COMPOSE_DOWN_OPTIONS
docker-stack-%:
$(foreach stack, $(STACK), $(call make,docker-compose-$* STACK=$(stack) APP_NAME=$(subst _,,$(subst -,,$(subst .,,$(call LOWERCASE,$(firstword $(subst /, ,$(stack)))))))))
# target docker-tag: Call docker-tag for each SERVICES # target docker-tag: Call docker-tag for each SERVICES
.PHONY: docker-tag .PHONY: docker-tag
docker-tag: stack docker-tag: stack
+6 -1
View File
@@ -1,8 +1,11 @@
# target ufw: Call ufw ARGS # target ufw: Call ufw ARGS
.PHONY: ufw .PHONY: ufw
ufw: ufw:
$(call ufw,$(ARGS)) $(call ufw,$(ARGS))
ifeq ($(SETUP_UFW),true)
# target ufw-bootstrap: Eval ufw-docker app variables # target ufw-bootstrap: Eval ufw-docker app variables
ufw-bootstrap: ufw-bootstrap:
$(eval APP_COMPOSE_FILE :=) $(eval APP_COMPOSE_FILE :=)
@@ -33,7 +36,7 @@ ufw-install:
# target ufw-up: Start ufw-docker docker # target ufw-up: Start ufw-docker docker
ufw-up: DOCKER_RUN_NETWORK := ufw-up: DOCKER_RUN_NETWORK :=
ufw-up: DOCKER_RUN_OPTIONS := --restart always -d --cap-add NET_ADMIN -v /etc/ufw:/etc/ufw $(if wildcard /etc/default/ufw,-v /etc/default/ufw:/etc/default/ufw) --network host ufw-up: DOCKER_RUN_OPTIONS := --restart always -d --cap-add NET_ADMIN -v /etc/ufw:/etc/ufw $(if $(wildcard /etc/default/ufw),-v /etc/default/ufw:/etc/default/ufw) --network host
ufw-up: ufw-up:
$(call app-up) $(call app-up)
@@ -65,3 +68,5 @@ ufw-%:
$(call make,ufw-$(command) STACK="$(stack)") \ $(call make,ufw-$(command) STACK="$(stack)") \
) \ ) \
) )
endif
+9 -3
View File
@@ -6,6 +6,11 @@ DOCKER_IMAGE ?= $(USER_DOCKER_IMAGE)
DOCKER_MACHINE ?= $(shell docker run --rm alpine uname -m 2>/dev/null) DOCKER_MACHINE ?= $(shell docker run --rm alpine uname -m 2>/dev/null)
DOCKER_NAME ?= $(USER_DOCKER_NAME) DOCKER_NAME ?= $(USER_DOCKER_NAME)
DOCKER_NETWORK ?= $(if $(USER_STACK),$(USER),$(DOCKER_NETWORK_PRIVATE)) DOCKER_NETWORK ?= $(if $(USER_STACK),$(USER),$(DOCKER_NETWORK_PRIVATE))
# https://github.com/moby/libnetwork/issues/2093
# network interface are attached ordered by name
# default should be first to prevent cross stack
# connections to other services with same name
DOCKER_NETWORK_DEFAULT ?= _$(COMPOSE_PROJECT_NAME)
DOCKER_NETWORK_PRIVATE ?= $(USER)-$(ENV) DOCKER_NETWORK_PRIVATE ?= $(USER)-$(ENV)
DOCKER_NETWORK_PUBLIC ?= $(HOSTNAME) DOCKER_NETWORK_PUBLIC ?= $(HOSTNAME)
# DOCKER_RUN: if empty, run system command, else run it in a docker # DOCKER_RUN: if empty, run system command, else run it in a docker
@@ -16,15 +21,16 @@ DOCKER_RUN_NETWORK += --network $(DOCKER_NETWORK)
DOCKER_RUN_OPTIONS += --rm DOCKER_RUN_OPTIONS += --rm
DOCKER_RUN_VOLUME ?= $(patsubst %,-v %,$(DOCKER_VOLUME)) DOCKER_RUN_VOLUME ?= $(patsubst %,-v %,$(DOCKER_VOLUME))
DOCKER_RUN_WORKDIR ?= $(if $(DOCKER_WORKDIR),-w $(DOCKER_WORKDIR)) DOCKER_RUN_WORKDIR ?= $(if $(DOCKER_WORKDIR),-w $(DOCKER_WORKDIR))
DOCKER_SOCKET_LOCATION ?= /var/run/docker.sock
DOCKER_SYSTEM ?= $(shell docker run --rm alpine uname -s 2>/dev/null) DOCKER_SYSTEM ?= $(shell docker run --rm alpine uname -s 2>/dev/null)
DOCKER_VOLUME ?= /var/run/docker.sock:/var/run/docker.sock DOCKER_VOLUME ?= $(DOCKER_SOCKET_LOCATION):/var/run/docker.sock
DOCKER_WORKDIR ?= $(PWD) DOCKER_WORKDIR ?= $(PWD)
ENV_VARS += DOCKER_MACHINE DOCKER_NETWORK DOCKER_NETWORK_PRIVATE DOCKER_NETWORK_PUBLIC DOCKER_SYSTEM HOST_COMPOSE_PROJECT_NAME HOST_COMPOSE_SERVICE_NAME HOST_DOCKER_REPOSITORY HOST_DOCKER_VOLUME HOST_GID HOST_UID USER_COMPOSE_PROJECT_NAME USER_COMPOSE_SERVICE_NAME USER_DOCKER_IMAGE USER_DOCKER_NAME USER_DOCKER_REPOSITORY USER_DOCKER_VOLUME ENV_VARS += DOCKER_MACHINE DOCKER_NETWORK DOCKER_NETWORK_DEFAULT DOCKER_NETWORK_PRIVATE DOCKER_NETWORK_PUBLIC DOCKER_SOCKET_LOCATION DOCKER_SYSTEM HOST_COMPOSE_PROJECT_NAME HOST_COMPOSE_SERVICE_NAME HOST_DOCKER_REPOSITORY HOST_DOCKER_VOLUME HOST_GID HOST_UID USER_COMPOSE_PROJECT_NAME USER_COMPOSE_SERVICE_NAME USER_DOCKER_IMAGE USER_DOCKER_NAME USER_DOCKER_REPOSITORY USER_DOCKER_VOLUME
HOST_COMPOSE_PROJECT_NAME ?= $(HOSTNAME) HOST_COMPOSE_PROJECT_NAME ?= $(HOSTNAME)
HOST_COMPOSE_SERVICE_NAME ?= $(subst _,-,$(HOST_COMPOSE_PROJECT_NAME)) HOST_COMPOSE_SERVICE_NAME ?= $(subst _,-,$(HOST_COMPOSE_PROJECT_NAME))
HOST_DOCKER_REPOSITORY ?= $(subst -,/,$(subst _,/,$(HOST_COMPOSE_PROJECT_NAME))) HOST_DOCKER_REPOSITORY ?= $(subst -,/,$(subst _,/,$(HOST_COMPOSE_PROJECT_NAME)))
HOST_DOCKER_VOLUME ?= $(HOST_COMPOSE_PROJECT_NAME) HOST_DOCKER_VOLUME ?= $(HOST_COMPOSE_PROJECT_NAME)
HOST_GID ?= 100 HOST_GID ?= $(HOST_UID)
HOST_UID ?= 123 HOST_UID ?= 123
HOST_STACK ?= $(filter host,$(firstword $(subst /, ,$(STACK)))) HOST_STACK ?= $(filter host,$(firstword $(subst /, ,$(STACK))))
MYOS_STACK ?= $(MYOS)/stack/myos MYOS_STACK ?= $(MYOS)/stack/myos
+43 -5
View File
@@ -76,7 +76,7 @@ IGNORE_VERBOSE ?= false
INSTALL ?= $(RUN) $(SUDO) $(subst &&,&& $(RUN) $(SUDO),$(INSTALL_CMD)) INSTALL ?= $(RUN) $(SUDO) $(subst &&,&& $(RUN) $(SUDO),$(INSTALL_CMD))
INSTALL_CMDS ?= APK_INSTALL APT_INSTALL INSTALL_CMDS ?= APK_INSTALL APT_INSTALL
$(foreach cmd,$(INSTALL_CMDS),$(if $(CMD_$(cmd)),$(eval INSTALL_CMD ?= $(CMD_$(cmd))))) $(foreach cmd,$(INSTALL_CMDS),$(if $(CMD_$(cmd)),$(eval INSTALL_CMD ?= $(CMD_$(cmd)))))
LOG_LEVEL ?= $(if $(DEBUG),debug,$(if $(VERBOSE),info,error)) LOG_LEVEL := $(or $(if $(DEBUG),debug),$(if $(VERBOSE),info),error)
MAIL ?= $(GIT_AUTHOR_EMAIL) MAIL ?= $(GIT_AUTHOR_EMAIL)
MAKE_ARGS = $(foreach var,$(MAKE_VARS),$(if $($(var)),$(var)='$($(var))')) MAKE_ARGS = $(foreach var,$(MAKE_VARS),$(if $($(var)),$(var)='$($(var))'))
MAKE_SUBDIRS ?= $(if $(filter myos,$(MYOS)),monorepo,$(if $(APP),apps $(foreach type,$(APP_LOAD),$(if $(wildcard $(MAKE_DIR)/apps/$(type)),apps/$(type))))) MAKE_SUBDIRS ?= $(if $(filter myos,$(MYOS)),monorepo,$(if $(APP),apps $(foreach type,$(APP_LOAD),$(if $(wildcard $(MAKE_DIR)/apps/$(type)),apps/$(type)))))
@@ -90,7 +90,7 @@ MAKE_OLDFILE ?= $@
MAKE_TARGETS ?= $(filter-out $(.VARIABLES),$(shell $(MAKE) -qp 2>/dev/null |awk -F':' '/^[a-zA-Z0-9][^$$\#\/\t=]*:([^=]|$$)/ {print $$1}' 2>/dev/null |sort -u)) MAKE_TARGETS ?= $(filter-out $(.VARIABLES),$(shell $(MAKE) -qp 2>/dev/null |awk -F':' '/^[a-zA-Z0-9][^$$\#\/\t=]*:([^=]|$$)/ {print $$1}' 2>/dev/null |sort -u))
MAKE_UNIXTIME_START := $(shell date -u +'%s' 2>/dev/null) MAKE_UNIXTIME_START := $(shell date -u +'%s' 2>/dev/null)
MAKE_UNIXTIME_CURRENT = $(shell date -u "+%s" 2>/dev/null) MAKE_UNIXTIME_CURRENT = $(shell date -u "+%s" 2>/dev/null)
MAKE_VARS := ENV COMPOSE_FILE DOCKER_COMPOSE DOCKER_MACHINE DOCKER_SERVICES DOCKER_SYSTEM MAKE_VARS := ENV COMPOSE_FILE DOCKER_COMPOSE DOCKER_SERVICES
MAKECMDARGS ?= apps-install install-app MAKECMDARGS ?= apps-install install-app
MONOREPO ?= $(if $(filter myos,$(MYOS)),$(notdir $(CURDIR)),$(if $(APP),$(notdir $(realpath $(CURDIR)/..)))) MONOREPO ?= $(if $(filter myos,$(MYOS)),$(notdir $(CURDIR)),$(if $(APP),$(notdir $(realpath $(CURDIR)/..))))
MONOREPO_DIR ?= $(if $(MONOREPO),$(if $(filter myos,$(MYOS)),$(realpath $(CURDIR)),$(if $(APP),$(realpath $(CURDIR)/..)))) MONOREPO_DIR ?= $(if $(MONOREPO),$(if $(filter myos,$(MYOS)),$(realpath $(CURDIR)),$(if $(APP),$(realpath $(CURDIR)/..))))
@@ -136,6 +136,11 @@ MACHINE ?= $(shell uname -m 2>/dev/null)
ifeq ($(SYSTEM),Darwin) ifeq ($(SYSTEM),Darwin)
SED_SUFFIX := '' SED_SUFFIX := ''
STAT_FORMAT_ARG := -f
STAT_FORMAT_FILE := '%a %N'
else
STAT_FORMAT_ARG := -c
STAT_FORMAT_FILE := '%Y %n'
endif endif
# include .env files # include .env files
@@ -169,6 +174,30 @@ INFO = $(if $(VERBOSE),$(if $(filter-out true,$(IGNORE_VERBOSE)), \
&& printf '\n' >&$(INFO_FD) \ && printf '\n' >&$(INFO_FD) \
)) ))
# macro base64t: Print trimed base64 encoded string from $1
base64t = $(shell echo -n '$(1)' |openssl enc -A -base64 |sed 's/+/-/g;s|/|_|g;s/=*$$//;')
# macro hs256: Print hmac sha256 digest from string $1 with key $2
hs256 = $(shell echo -n '$(1)' |openssl dgst -sha256 -binary -hmac '$(2)')
b64_hs256 = $(shell echo -n '$(1)' |openssl dgst -sha256 -binary -hmac '$(2)' |base64 |sed 's/+/-/g;s|/|_|g;s/=*$$//;')
# macro rs256: Print sha256 digest from string $1 signed by key $2
rs256 = $(shell echo -n '$(1)' |openssl dgst -sha256 -binary -sign '$(2)')
JWT_HEADER = {"alg":"HS256","typ":"JWT"}
# macro JWT: Print Json Web Token for header $1 payload $2 and key $3
JWT = $(strip \
$(eval header := $(or $(1),$(JWT_HEADER))) \
$(eval payload := $(or $(2),$(JWT_PAYLOAD))) \
$(eval secret := $(or $(3),$(JWT_SECRET))) \
$(eval b64_header := $(call base64t,$(header))) \
$(eval b64_payload := $(call base64t,$(payload))) \
$(eval b64_signature := $(call base64t,$(call hs256,$(b64_header).$(b64_payload),$(secret)))) \
$(eval b64_signature := $(call b64_hs256,$(b64_header).$(b64_payload),$(secret))) \
$(if $(DEBUG),$(header) - $(payload) - $(secret) --- ) \
$(b64_header).$(b64_payload).$(b64_signature))
# macro RESU: Print USER associated to MAIL # macro RESU: Print USER associated to MAIL
RESU = $(strip \ RESU = $(strip \
$(if $(findstring @,$(MAIL)), \ $(if $(findstring @,$(MAIL)), \
@@ -222,14 +251,17 @@ force = $$(while true; do \
&& $(RUN) $(1) || sleep 1; done \ && $(RUN) $(1) || sleep 1; done \
) )
# macro gid: Return GID of group 1 # macro gid: Return GID of group $1
gid = $(shell awk -F':' '$$1 == "$(1)" {print $$3}' /etc/group 2>/dev/null) gid = $(shell awk -F':' '$$1 == "$(1)" {print $$3}' /etc/group 2>/dev/null)
# macro newenv: Return VAR from file $2 that does not exist in file $1, matching on VAR= part of lines
newenv = $(shell awk -F'=' 'NR == FNR {if($$1 !~ /^(\#|$$)/) { A[$$1]; next }} !($$1 in A) {if($$1 !~ /^(\#|$$)/) { print $$1 }}' $(1) $(2) 2>/dev/null)
# macro newer: Return the newest file # macro newer: Return the newest file
newer = $(shell stat -c '%Y %n' $(1) $(2) $(if $(DEBUG),,2>/dev/null) |sort -n |tail -n1 |awk '{print $$2}') newer = $(shell stat $(STAT_FORMAT_ARG) $(STAT_FORMAT_FILE) $(1) $(2) $(if $(DEBUG),,2>/dev/null) |sort -n |tail -n1 |awk '{print $$2}')
# older newer: Return the oldest file # older newer: Return the oldest file
older = $(shell stat -c '%Y %n' $(1) $(2) $(if $(DEBUG),,2>/dev/null) |sort -n |head -n1 |awk '{print $$2}') older = $(shell stat $(STAT_FORMAT_ARG) $(STAT_FORMAT_FILE) $(1) $(2) $(if $(DEBUG),,2>/dev/null) |sort -n |head -n1 |awk '{print $$2}')
# macro pop: Return last word of string 1 according to separator 2 # macro pop: Return last word of string 1 according to separator 2
pop = $(patsubst %$(or $(2),/)$(lastword $(subst $(or $(2),/), ,$(1))),%,$(1)) pop = $(patsubst %$(or $(2),/)$(lastword $(subst $(or $(2),/), ,$(1))),%,$(1))
@@ -237,6 +269,12 @@ pop = $(patsubst %$(or $(2),/)$(lastword $(subst $(or $(2),/), ,$(1))),%,$(1))
# macro sed: Run sed script 1 on file 2 # macro sed: Run sed script 1 on file 2
sed = $(RUN) sed -i $(SED_SUFFIX) '$(1)' $(2) sed = $(RUN) sed -i $(SED_SUFFIX) '$(1)' $(2)
# macro verle: Return true when version 1 <= 2
verle = [ -n "$(1)" ] && [ "$(1)" = "$(shell echo -e "$(1)\n$(2)" |sort -V |head -n1)" ]
# macro verlt: Return true when version 1 < 2
verlt = [ "$(1)" = "$(2)" ] && return 1 || $(call verlte,$(1),$(2))
# function conf: Extract variable=value line from configuration files # function conf: Extract variable=value line from configuration files
## it prints the line with variable 3 definition from block 2 in file 1 ## it prints the line with variable 3 definition from block 2 in file 1
define conf define conf
+8 -6
View File
@@ -11,7 +11,7 @@
## it removes file .env ## it removes file .env
.PHONY: .env-clean .PHONY: .env-clean
.env-clean: .env-clean:
$(RUN) rm -$(if $(FORCE),f,i) .env || true $(RUN) rm -$(if $(FORCE),f,i) $(ENV_FILE) || true
# target .env-update: Update file ENV_FILE # target .env-update: Update file ENV_FILE
## it updates file ENV_FILE with missing values from file ENV_DIST ## it updates file ENV_FILE with missing values from file ENV_DIST
@@ -51,8 +51,8 @@ define .env
$(eval env_file:=$(or $(1),.env)) $(eval env_file:=$(or $(1),.env))
$(eval env_dists:=$(wildcard $(or $(2),$(env_file).dist))) $(eval env_dists:=$(wildcard $(or $(2),$(env_file).dist)))
$(eval env_over:=$(wildcard $(or $(3),$(env_file).$(ENV)))) $(eval env_over:=$(wildcard $(or $(3),$(env_file).$(ENV))))
$(if $(FORCE)$(filter $(env_file),$(call newer,$(env_file) $(env_dists) $(env_over))), $(if $(FORCE)$(call newenv,$(env_file),$(env_dists) $(env_over))$(if $(wildcard $(env_file)),,FORCE),
,$(foreach env_dist,$(env_dists),$(call .env_update))) $(foreach env_dist,$(env_dists),$(call .env_update)))
endef endef
# function .env_update: Update .env file with values from .env.dist # function .env_update: Update .env file with values from .env.dist
@@ -74,10 +74,11 @@ endef
# create a new (empty if ENV_RESET is true) environment with env.args # create a new (empty if ENV_RESET is true) environment with env.args
# read environment variables and keep only those existing in .env.dist # read environment variables and keep only those existing in .env.dist
# add .env overrides variables definition # add .env overrides variables definition
# add .env.dist variables definition # add .env.dist variables definition when variable value is not the variable name itself
# remove empty lines or comments # remove empty lines or comments
# remove duplicate variables # remove duplicate variables
# replace variables in stdin with their value from the new environment # replace variables in stdin with their value from the new environment
# eval and replace commands in stdin with their result
# remove residual empty lines or comments # remove residual empty lines or comments
# sort alphabetically # sort alphabetically
# add variables definition to the .env file # add variables definition to the .env file
@@ -95,10 +96,11 @@ define .env_update
env $(env_reset) $(env.args) \ env $(env_reset) $(env.args) \
$$(env |awk -F '=' 'NR == FNR { if($$1 !~ /^(#|$$)/) { A[$$1]; next } } ($$1 in A)' $(env_dist) - \ $$(env |awk -F '=' 'NR == FNR { if($$1 !~ /^(#|$$)/) { A[$$1]; next } } ($$1 in A)' $(env_dist) - \
|cat - $(env_over) \ |cat - $(env_over) \
|cat - $(env_dist) \ |cat - $(env_dist) |awk -F '=' '{ if(match($$0,"[$$]{[^}]*}")) {var=substr($$0,RSTART+2,RLENGTH-3);if(var!=$$1) print} else print}' \
|sed -e /^$$/d -e /^#/d \ |sed -e /^$$/d -e /^#/d \
|awk -F '=' '!seen[$$1]++') \ |awk -F '=' '!seen[$$1]++') \
awk '{while(match($$0,"[$$]{[^}]*}")) {var=substr($$0,RSTART+2,RLENGTH-3);gsub("[$$]{"var"}",ENVIRON[var])} print}') \ awk '{while(match($$0,"[$$]{[^}]*}")) {var=substr($$0,RSTART+2,RLENGTH-3);if("$${"var"}"!=ENVIRON[var]){gsub("[$$]{"var"}",ENVIRON[var])}else{gsub("[$$]{"var"}","")}}; \
while(match($$0,"[$$]\\([^\\)]*\\)")) {cmd=substr($$0,RSTART+2,RLENGTH-3);cmd|getline var;gsub("[$$]\\([^\\)]*\\)",var);close(cmd)} print}') \
|sed -e /^$$/d -e /^#/d \ |sed -e /^$$/d -e /^#/d \
|sort \ |sort \
>> $(env_file) $(if $(VERBOSE)$(DEBUG),,2> /dev/null) ||:; >> $(env_file) $(if $(VERBOSE)$(DEBUG),,2> /dev/null) ||:;
+3 -3
View File
@@ -1,6 +1,6 @@
ENV_VARS += USER_IPFS_API_HTTPHEADERS_ACA_ORIGIN USER_IPFS_LETSENCRYPT_HOST USER_IPFS_PATH USER_IPFS_SERVICE_5001_TAGS USER_IPFS_SERVICE_8080_TAGS ENV_VARS += USER_IPFS_API_HTTPHEADERS_ACA_ORIGIN USER_IPFS_LETSENCRYPT_HOST USER_IPFS_PATH USER_IPFS_SERVICE_5001_TAGS USER_IPFS_SERVICE_8080_TAGS
USER_IPFS_API_HTTPHEADERS_ACA_ORIGIN ?= [$(call patsublist,%,"https://%",$(patsubst %/,%,$(USER_IPFS_SERVICE_8080_URIS)))] USER_IPFS_API_HTTPHEADERS_ACA_ORIGIN ?= [$(call patsublist,%,"https://%",$(patsubst %/,%,$(USER_IPFS_SERVICE_8080_URIS)))]
USER_IPFS_LETSENCRYPT_HOST ?= $(subst $(space),$(comma),$(call uriprefix,USER_IPFS,,$(USER_HOST))) USER_IPFS_LETSENCRYPT_HOST ?= $(subst $(space),$(comma),$(call uri,USER_IPFS,,$(USER_HOST)))
USER_IPFS_PATH ?= $(USER_HOME)/.ipfs USER_IPFS_PATH ?= $(USER_HOME)/.ipfs
USER_IPFS_SERVICE_NAME ?= ipfs USER_IPFS_SERVICE_NAME ?= ipfs
USER_IPFS_SERVICE_5001_PATH ?= api/ USER_IPFS_SERVICE_5001_PATH ?= api/
@@ -8,8 +8,8 @@ USER_IPFS_SERVICE_5001_TAGS ?= $(or $(USER_IPFS_SERVICE_5001_TAGS_
USER_IPFS_SERVICE_5001_TAGS_LOCALHOST ?= $(filter %.localhost/$(USER_PATH)$(USER_IPFS_SERVICE_5001_PATH)$(url_suffix),$(call tagprefix,USER_IPFS,5001)) USER_IPFS_SERVICE_5001_TAGS_LOCALHOST ?= $(filter %.localhost/$(USER_PATH)$(USER_IPFS_SERVICE_5001_PATH)$(url_suffix),$(call tagprefix,USER_IPFS,5001))
USER_IPFS_SERVICE_5001_TAGS_PROXY_TCP ?= $(call patsublist,%,urlprefix-% proxy=tcp,$(USER_IPFS_SERVICE_PROXY_TCP)) USER_IPFS_SERVICE_5001_TAGS_PROXY_TCP ?= $(call patsublist,%,urlprefix-% proxy=tcp,$(USER_IPFS_SERVICE_PROXY_TCP))
USER_IPFS_SERVICE_5001_TAGS_URIS ?= $(strip $(if $(call servicenvs,USER_IPFS,5001,URIS),$(call urlprefix,$(USER_IPFS_SERVICE_5001_PATH),,$(call servicenvs,USER_IPFS,5001,URIS)))) USER_IPFS_SERVICE_5001_TAGS_URIS ?= $(strip $(if $(call servicenvs,USER_IPFS,5001,URIS),$(call urlprefix,$(USER_IPFS_SERVICE_5001_PATH),,$(call servicenvs,USER_IPFS,5001,URIS))))
USER_IPFS_SERVICE_5001_URIS ?= $(call uriprefix,USER_IPFS,5001,$(USER_URIS)) USER_IPFS_SERVICE_5001_URIS ?= $(call uri,USER_IPFS,5001,$(USER_URIS))
USER_IPFS_SERVICE_8080_OPTS ?= $(patsubst %/,%,$(if $(USER_PATH),strip=/$(USER_PATH))) USER_IPFS_SERVICE_8080_OPTS ?= $(patsubst %/,%,$(if $(USER_PATH),strip=/$(USER_PATH)))
USER_IPFS_SERVICE_8080_PATH ?= ipfs/ USER_IPFS_SERVICE_8080_PATH ?= ipfs/
USER_IPFS_SERVICE_8080_TAGS ?= $(call tagprefix,USER_IPFS,8080) USER_IPFS_SERVICE_8080_TAGS ?= $(call tagprefix,USER_IPFS,8080)
USER_IPFS_SERVICE_8080_URIS ?= $(call uriprefix,USER_IPFS,8080,$(USER_URIS)) USER_IPFS_SERVICE_8080_URIS ?= $(call uri,USER_IPFS,8080,$(USER_URIS))
+1 -1
View File
@@ -16,4 +16,4 @@ services:
- private - private
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock
+1 -1
View File
@@ -10,4 +10,4 @@ services:
- private - private
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock
+1 -1
View File
@@ -18,7 +18,7 @@ services:
volumes_from: volumes_from:
- nginx - nginx
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro
- acme:/etc/acme.sh - acme:/etc/acme.sh
- certs:/etc/nginx/certs - certs:/etc/nginx/certs
- html:/usr/share/nginx/html - html:/usr/share/nginx/html
+1 -1
View File
@@ -8,4 +8,4 @@ services:
image: willfarrell/autoheal:latest image: willfarrell/autoheal:latest
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock
+1 -1
View File
@@ -42,7 +42,7 @@ services:
restart: always restart: always
volumes: volumes:
- consul:/consul/data:delegated - consul:/consul/data:delegated
- /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock
volumes: volumes:
consul: consul:
+1 -1
View File
@@ -6,5 +6,5 @@ HOST_IPFS_SERVICE_5001_PATH ?= api/
HOST_IPFS_SERVICE_5001_TAGS ?= $(call tagprefix,HOST_IPFS,5001) HOST_IPFS_SERVICE_5001_TAGS ?= $(call tagprefix,HOST_IPFS,5001)
HOST_IPFS_SERVICE_8080_ENVS ?= host HOST_IPFS_SERVICE_8080_ENVS ?= host
HOST_IPFS_SERVICE_8080_TAGS ?= $(call urlprefix,,,$(HOST_IPFS_SERVICE_8080_URIS) $(call servicenvs,HOST_IPFS,8080,URIS)) HOST_IPFS_SERVICE_8080_TAGS ?= $(call urlprefix,,,$(HOST_IPFS_SERVICE_8080_URIS) $(call servicenvs,HOST_IPFS,8080,URIS))
HOST_IPFS_SERVICE_8080_URIS ?= $(call uriprefix,ipfs *.ipns *.ipfs) HOST_IPFS_SERVICE_8080_URIS ?= $(call uri,ipfs *.ipns *.ipfs)
HOST_IPFS_UFW_UPDATE ?= 4001/tcp 4001/udp 8080 HOST_IPFS_UFW_UPDATE ?= 4001/tcp 4001/udp 8080
+1 -1
View File
@@ -38,7 +38,7 @@ services:
- 443 - 443
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - ${DOCKER_SOCKET_LOCATION}:/tmp/docker.sock:ro
- certs:/etc/nginx/certs - certs:/etc/nginx/certs
- html:/usr/share/nginx/html - html:/usr/share/nginx/html
- htpasswd:/etc/nginx/htpasswd - htpasswd:/etc/nginx/htpasswd
+1 -1
View File
@@ -16,7 +16,7 @@ services:
- 9000 - 9000
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock
- portainer:/data - portainer:/data
volumes: volumes:
+1 -1
View File
@@ -25,4 +25,4 @@ services:
network_mode: host network_mode: host
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock - ${DOCKER_SOCKET_LOCATION}:/tmp/docker.sock
+1 -1
View File
@@ -8,7 +8,7 @@ IPFS_SERVICE_5001_PATH ?= api/
IPFS_SERVICE_5001_TAGS ?= $(call tagprefix,ipfs,5001) IPFS_SERVICE_5001_TAGS ?= $(call tagprefix,ipfs,5001)
IPFS_SERVICE_8080_CHECK_HTTP ?= /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn IPFS_SERVICE_8080_CHECK_HTTP ?= /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn
IPFS_SERVICE_8080_TAGS ?= $(call urlprefix,,,$(IPFS_SERVICE_8080_URIS)) IPFS_SERVICE_8080_TAGS ?= $(call urlprefix,,,$(IPFS_SERVICE_8080_URIS))
IPFS_SERVICE_8080_URIS ?= $(call uriprefix,ipfs *.ipns) IPFS_SERVICE_8080_URIS ?= $(call uri,ipfs *.ipns *.ipfs)
IPFS_VERSION ?= 0.32.1 IPFS_VERSION ?= 0.32.1
.PHONY: bootstrap-stack-ipfs .PHONY: bootstrap-stack-ipfs
+2
View File
@@ -1,4 +1,6 @@
networks: networks:
default:
name: ${DOCKER_NETWORK_DEFAULT:-_default}
private: private:
external: true external: true
name: ${DOCKER_NETWORK_PRIVATE:-docker} name: ${DOCKER_NETWORK_PRIVATE:-docker}
+1 -1
View File
@@ -28,7 +28,7 @@ services:
- 443 - 443
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - ${DOCKER_SOCKET_LOCATION}:/tmp/docker.sock:ro
- certs:/etc/nginx/certs - certs:/etc/nginx/certs
- html:/usr/share/nginx/html - html:/usr/share/nginx/html
- log:/var/log/nginx - log:/var/log/nginx
+1 -1
View File
@@ -15,7 +15,7 @@ services:
- 9000 - 9000
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock
- portainer:/data - portainer:/data
volumes: volumes:
+1 -1
View File
@@ -1,5 +1,5 @@
ENV_VARS += PROMETHEUS_BLACKBOX_PRIMARY_TARGETS PROMETHEUS_BLACKBOX_SECONDARY_TARGETS PROMETHEUS_SERVICE_9090_TAGS ENV_VARS += PROMETHEUS_BLACKBOX_PRIMARY_TARGETS PROMETHEUS_BLACKBOX_SECONDARY_TARGETS PROMETHEUS_SERVICE_9090_TAGS
PROMETHEUS_BLACKBOX_PRIMARY_TARGETS ?= $(patsubst %,https://%,$(DOMAIN)) PROMETHEUS_BLACKBOX_PRIMARY_TARGETS ?= $(patsubst %,https://%,$(DOMAIN))
PROMETHEUS_BLACKBOX_SECONDARY_TARGETS ?= $(patsubst %,https://%,$(APP_URIS)) PROMETHEUS_BLACKBOX_SECONDARY_TARGETS ?= $(patsubst %,https://%,$(APP_URI))
PROMETHEUS_SERVICE_9090_NAME ?= prometheus PROMETHEUS_SERVICE_9090_NAME ?= prometheus
PROMETHEUS_SERVICE_9090_TAGS ?= $(call tagprefix,prometheus,9090) PROMETHEUS_SERVICE_9090_TAGS ?= $(call tagprefix,prometheus,9090)
+1 -1
View File
@@ -20,4 +20,4 @@ services:
- 514 - 514
restart: always restart: always
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock - ${DOCKER_SOCKET_LOCATION}:/tmp/docker.sock
+72
View File
@@ -0,0 +1,72 @@
DOCKER_SOCKET_LOCATION=/var/run/docker.sock
SUPABASE_DASHBOARD_PASSWORD=$(head -c11 /dev/random |base64 |sed 's/+/-/g;s/\//_/g;s/=*$//;')
SUPABASE_JWT_IAT=${SUPABASE_JWT_IAT}
# JWT_SECRET: 40 chars
SUPABASE_JWT_SECRET=$(head -c30 /dev/random |base64 |sed 's/+/-/g;s/\//_/g;s/=*$//;')
SUPABASE_LOGFLARE_API_KEY=$(head -c42 /dev/random |base64 |sed 's/+/-/g;s/\//_/g;s/=*$//;')
# DB_ENC_KEY: 16 chars
SUPABASE_POSTGRES_DB_ENC_KEY=$(head -c12 /dev/random |base64 |sed 's/+/-/g;s/\//_/g;s/=*$//;')
SUPABASE_POSTGRES_PASSWORD=$(head -c42 /dev/random |base64 |sed 's/+/-/g;s/\//_/g;s/=*$//;')
# SECRET_KEY_BASE: 64 chars
SUPABASE_SECRET_KEY_BASE=$(head -c48 /dev/random |base64 |sed 's/+/-/g;s/\//_/g;s/=*$//;')
# VAULT_ENC_KEY: 32 chars
SUPABASE_VAULT_ENC_KEY=$(head -c24 /dev/random |base64 |sed 's/+/-/g;s/\//_/g;s/=*$//;')
## General
# SUPABASE_SITE_URL=http://localhost:3000
# SUPABASE_ADDITIONAL_REDIRECT_URLS=
# SUPABASE_JWT_EXPIRY=3600
# SUPABASE_DISABLE_SIGNUP=false
# SUPABASE_API_EXTERNAL_URL=http://kong:8000
# SUPABASE_POSTGRES_HOST=supabase-db
# SUPABASE_POSTGRES_DB=postgres
# SUPABASE_POSTGRES_PORT=5432
# SUPABASE_POSTGRES_USER=postgres
# SUPABASE_POSTGRES_PORT=5432
# SUPABASE_POOLER_PROXY_PORT_TRANSACTION=6543
# SUPABASE_POOLER_DEFAULT_POOL_SIZE=20
# SUPABASE_POOLER_MAX_CLIENT_CONN=100
# SUPABASE_POOLER_TENANT_ID=your-tenant-id
# SUPABASE_KONG_HTTP_PORT=8000
# SUPABASE_KONG_HTTPS_PORT=8443
## API - Configuration for PostgREST.
# SUPABASE_PGRST_DB_SCHEMAS=public,storage,graphql_public
## Auth - Configuration for the GoTrue authentication server.
# SUPABASE_GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI=pg-functions://postgres/public/mfa_verification_attempt
# SUPABASE_GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS=<standard-base64-secret>
# SUPABASE_GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI=pg-functions://postgres/public/mfa_verification_attempt
# SUPABASE_GOTRUE_HOOK_SEND_SMS_URI=pg-functions://postgres/public/custom_access_token_hook
# SUPABASE_GOTRUE_HOOK_SEND_SMS_SECRETS=v1,whsec_VGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgc2hvcnRlciBCYXNlNjQgc3RyaW5n
# SUPABASE_GOTRUE_HOOK_SEND_EMAIL_URI=http://host.docker.internal:54321/functions/v1/email_sender
# SUPABASE_GOTRUE_HOOK_SEND_EMAIL_SECRETS=v1,whsec_VGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgc2hvcnRlciBCYXNlNjQgc3RyaW5n
## Mailer Config
# SUPABASE_MAILER_URLPATHS_CONFIRMATION="/auth/v1/verify"
# SUPABASE_MAILER_URLPATHS_INVITE="/auth/v1/verify"
# SUPABASE_MAILER_URLPATHS_RECOVERY="/auth/v1/verify"
# SUPABASE_MAILER_URLPATHS_EMAIL_CHANGE="/auth/v1/verify"
## Email auth
# SUPABASE_ENABLE_EMAIL_SIGNUP=true
# SUPABASE_ENABLE_EMAIL_AUTOCONFIRM=false
# SUPABASE_SMTP_ADMIN_EMAIL=admin@${DOMAINNAME}
# SUPABASE_SMTP_HOST=mail
# SUPABASE_SMTP_PORT=25
# SUPABASE_SMTP_USER=
# SUPABASE_SMTP_PASS=
# SUPABASE_SMTP_SENDER_NAME=supabase@${DOMAINNAME}
# SUPABASE_ENABLE_ANONYMOUS_USERS=false
## Phone auth
# SUPABASE_ENABLE_PHONE_SIGNUP=true
# SUPABASE_ENABLE_PHONE_AUTOCONFIRM=true
## Studio - Configuration for the Dashboard
# SUPABASE_STUDIO_DEFAULT_ORGANIZATION=Default Organization
# SUPABASE_STUDIO_DEFAULT_PROJECT=Default Project
## replace if you intend to use Studio outside of localhost
# SUPABASE_PUBLIC_URL=http://localhost:8000
## Enable webp support
# SUPABASE_IMGPROXY_ENABLE_WEBP_DETECTION=true
## Add your OpenAI API key to enable SQL Editor Assistant
# SUPABASE_OPENAI_API_KEY=
## Functions - Configuration for Functions
# SUPABASE_FUNCTIONS_VERIFY_JWT=false
# SUPABASE_GOOGLE_PROJECT_ID=
# SUPABASE_GOOGLE_PROJECT_NUMBER=
+123
View File
@@ -0,0 +1,123 @@
############
# Secrets
# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION
############
POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password
JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
DASHBOARD_USERNAME=supabase
DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated
SECRET_KEY_BASE=UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
VAULT_ENC_KEY=your-encryption-key-32-chars-min
############
# Database - You can change these to any PostgreSQL database that has logical replication enabled.
############
POSTGRES_HOST=db
POSTGRES_DB=postgres
POSTGRES_PORT=5432
# default user is postgres
############
# Supavisor -- Database pooler
############
POOLER_PROXY_PORT_TRANSACTION=6543
POOLER_DEFAULT_POOL_SIZE=20
POOLER_MAX_CLIENT_CONN=100
POOLER_TENANT_ID=your-tenant-id
############
# API Proxy - Configuration for the Kong Reverse proxy.
############
KONG_HTTP_PORT=8000
KONG_HTTPS_PORT=8443
############
# API - Configuration for PostgREST.
############
PGRST_DB_SCHEMAS=public,storage,graphql_public
############
# Auth - Configuration for the GoTrue authentication server.
############
## General
SITE_URL=http://localhost:3000
ADDITIONAL_REDIRECT_URLS=
JWT_EXPIRY=3600
DISABLE_SIGNUP=false
API_EXTERNAL_URL=http://localhost:8000
## Mailer Config
MAILER_URLPATHS_CONFIRMATION="/auth/v1/verify"
MAILER_URLPATHS_INVITE="/auth/v1/verify"
MAILER_URLPATHS_RECOVERY="/auth/v1/verify"
MAILER_URLPATHS_EMAIL_CHANGE="/auth/v1/verify"
## Email auth
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=false
SMTP_ADMIN_EMAIL=admin@example.com
SMTP_HOST=supabase-mail
SMTP_PORT=2500
SMTP_USER=fake_mail_user
SMTP_PASS=fake_mail_password
SMTP_SENDER_NAME=fake_sender
ENABLE_ANONYMOUS_USERS=false
## Phone auth
ENABLE_PHONE_SIGNUP=true
ENABLE_PHONE_AUTOCONFIRM=true
############
# Studio - Configuration for the Dashboard
############
STUDIO_DEFAULT_ORGANIZATION=Default Organization
STUDIO_DEFAULT_PROJECT=Default Project
STUDIO_PORT=3000
# replace if you intend to use Studio outside of localhost
SUPABASE_PUBLIC_URL=http://localhost:8000
# Enable webp support
IMGPROXY_ENABLE_WEBP_DETECTION=true
# Add your OpenAI API key to enable SQL Editor Assistant
OPENAI_API_KEY=
############
# Functions - Configuration for Functions
############
# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet.
FUNCTIONS_VERIFY_JWT=false
############
# Logs - Configuration for Logflare
# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction
############
LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key
# Change vector.toml sinks to reflect this change
LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key
# Docker socket location - this value will differ depending on your OS
DOCKER_SOCKET_LOCATION=/var/run/docker.sock
# Google Cloud Project details
GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID
GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER
+5
View File
@@ -0,0 +1,5 @@
volumes/db/data
volumes/storage
.env
test.http
docker-compose.override.yml
+3
View File
@@ -0,0 +1,3 @@
# Supabase Docker
This is a minimal Docker Compose setup for self-hosting Supabase. Follow the steps [here](https://supabase.com/docs/guides/hosting/docker) to get started.
+48
View File
@@ -0,0 +1,48 @@
create table profiles (
id uuid references auth.users not null,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
primary key (id),
unique(username),
constraint username_length check (char_length(username) >= 3)
);
alter table profiles enable row level security;
create policy "Public profiles are viewable by the owner."
on profiles for select
using ( auth.uid() = id );
create policy "Users can insert their own profile."
on profiles for insert
with check ( auth.uid() = id );
create policy "Users can update own profile."
on profiles for update
using ( auth.uid() = id );
-- Set up Realtime
begin;
drop publication if exists supabase_realtime;
create publication supabase_realtime;
commit;
alter publication supabase_realtime add table profiles;
-- Set up Storage
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
create policy "Avatar images are publicly accessible."
on storage.objects for select
using ( bucket_id = 'avatars' );
create policy "Anyone can upload an avatar."
on storage.objects for insert
with check ( bucket_id = 'avatars' );
create policy "Anyone can update an avatar."
on storage.objects for update
with check ( bucket_id = 'avatars' );
+34
View File
@@ -0,0 +1,34 @@
version: "3.8"
services:
studio:
build:
context: ..
dockerfile: studio/Dockerfile
target: dev
ports:
- 8082:8082
mail:
container_name: supabase-mail
image: inbucket/inbucket:3.0.3
ports:
- '2500:2500' # SMTP
- '9000:9000' # web interface
- '1100:1100' # POP3
auth:
environment:
- GOTRUE_SMTP_USER=
- GOTRUE_SMTP_PASS=
meta:
ports:
- 5555:8080
db:
restart: 'no'
volumes:
# Always use a fresh database when developing
- /var/lib/postgresql/data
# Seed data should be inserted last (alphabetical order)
- ./dev/data.sql:/docker-entrypoint-initdb.d/seed.sql
storage:
volumes:
- /var/lib/storage
+94
View File
@@ -0,0 +1,94 @@
services:
minio:
image: minio/minio
ports:
- '9000:9000'
- '9001:9001'
environment:
MINIO_ROOT_USER: supa-storage
MINIO_ROOT_PASSWORD: secret1234
command: server --console-address ":9001" /data
healthcheck:
test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
interval: 2s
timeout: 10s
retries: 5
volumes:
- ./volumes/storage:/data:z
minio-createbucket:
image: minio/mc
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
/usr/bin/mc alias set supa-minio http://minio:9000 supa-storage secret1234;
/usr/bin/mc mb supa-minio/stub;
exit 0;
"
storage:
container_name: supabase-storage
image: supabase/storage-api:v1.11.13
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
rest:
condition: service_started
imgproxy:
condition: service_started
minio:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://localhost:5000/status"
]
timeout: 5s
interval: 5s
retries: 3
restart: unless-stopped
environment:
ANON_KEY: ${ANON_KEY}
SERVICE_KEY: ${SERVICE_ROLE_KEY}
POSTGREST_URL: http://rest:3000
PGRST_JWT_SECRET: ${JWT_SECRET}
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
FILE_SIZE_LIMIT: 52428800
STORAGE_BACKEND: s3
GLOBAL_S3_BUCKET: stub
GLOBAL_S3_ENDPOINT: http://minio:9000
GLOBAL_S3_PROTOCOL: http
GLOBAL_S3_FORCE_PATH_STYLE: true
AWS_ACCESS_KEY_ID: supa-storage
AWS_SECRET_ACCESS_KEY: secret1234
AWS_DEFAULT_REGION: stub
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
TENANT_ID: stub
# TODO: https://github.com/supabase/storage-api/issues/55
REGION: stub
ENABLE_IMAGE_TRANSFORMATION: "true"
IMGPROXY_URL: http://imgproxy:5001
volumes:
- ./volumes/storage:/var/lib/storage:z
imgproxy:
container_name: supabase-imgproxy
image: darthsim/imgproxy:v3.8.0
healthcheck:
test: [ "CMD", "imgproxy", "health" ]
timeout: 5s
interval: 5s
retries: 3
environment:
IMGPROXY_BIND: ":5001"
IMGPROXY_USE_ETAG: "true"
IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION}
+526
View File
@@ -0,0 +1,526 @@
# Usage
# Start: docker compose up
# With helpers: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up
# Stop: docker compose down
# Destroy: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down -v --remove-orphans
# Reset everything: ./reset.sh
name: supabase
services:
studio:
container_name: supabase-studio
image: supabase/studio:20250113-83c9420
restart: unless-stopped
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://studio:3000/api/profile').then((r) => {if (r.status !== 200) throw new Error(r.status)})"
]
timeout: 10s
interval: 5s
retries: 3
depends_on:
analytics:
condition: service_healthy
environment:
STUDIO_PG_META_URL: http://meta:8080
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION}
DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
SUPABASE_URL: http://kong:8000
SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL}
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
AUTH_JWT_SECRET: ${JWT_SECRET}
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
LOGFLARE_URL: http://analytics:4000
NEXT_PUBLIC_ENABLE_LOGS: true
# Comment to use Big Query backend for analytics
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
# Uncomment to use Big Query backend for analytics
# NEXT_ANALYTICS_BACKEND_PROVIDER: bigquery
kong:
container_name: supabase-kong
image: kong:2.8.1
restart: unless-stopped
ports:
- ${KONG_HTTP_PORT}:8000/tcp
- ${KONG_HTTPS_PORT}:8443/tcp
volumes:
# https://github.com/supabase/supabase/issues/12661
- ./volumes/api/kong.yml:/home/kong/temp.yml:ro
depends_on:
analytics:
condition: service_healthy
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
# https://github.com/supabase/cli/issues/14
KONG_DNS_ORDER: LAST,A,CNAME
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
# https://unix.stackexchange.com/a/294837
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.167.0
restart: unless-stopped
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://localhost:9999/health"
]
timeout: 5s
interval: 5s
retries: 3
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
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}@${POSTGRES_HOST}:${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}
# Uncomment to bypass nonce check in ID Token flow. Commonly set to true when using Google Sign In on mobile.
# GOTRUE_EXTERNAL_SKIP_NONCE_CHECK: true
# GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: true
# GOTRUE_SMTP_MAX_FREQUENCY: 1s
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
GOTRUE_SMTP_HOST: ${SMTP_HOST}
GOTRUE_SMTP_PORT: ${SMTP_PORT}
GOTRUE_SMTP_USER: ${SMTP_USER}
GOTRUE_SMTP_PASS: ${SMTP_PASS}
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE}
GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION}
GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY}
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE}
GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP}
GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM}
# Uncomment to enable custom access token hook. Please see: https://supabase.com/docs/guides/auth/auth-hooks for full list of hooks and additional details about custom_access_token_hook
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED: "true"
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI: "pg-functions://postgres/public/custom_access_token_hook"
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS: "<standard-base64-secret>"
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED: "true"
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI: "pg-functions://postgres/public/mfa_verification_attempt"
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED: "true"
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI: "pg-functions://postgres/public/password_verification_attempt"
# GOTRUE_HOOK_SEND_SMS_ENABLED: "false"
# GOTRUE_HOOK_SEND_SMS_URI: "pg-functions://postgres/public/custom_access_token_hook"
# GOTRUE_HOOK_SEND_SMS_SECRETS: "v1,whsec_VGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgc2hvcnRlciBCYXNlNjQgc3RyaW5n"
# GOTRUE_HOOK_SEND_EMAIL_ENABLED: "false"
# GOTRUE_HOOK_SEND_EMAIL_URI: "http://host.docker.internal:54321/functions/v1/email_sender"
# GOTRUE_HOOK_SEND_EMAIL_SECRETS: "v1,whsec_VGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgc2hvcnRlciBCYXNlNjQgc3RyaW5n"
rest:
container_name: supabase-rest
image: postgrest/postgrest:v12.2.0
restart: unless-stopped
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
environment:
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${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"
]
realtime:
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
container_name: realtime-dev.supabase-realtime
image: supabase/realtime:v2.34.7
restart: unless-stopped
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"curl",
"-sSfL",
"--head",
"-o",
"/dev/null",
"-H",
"Authorization: Bearer ${ANON_KEY}",
"http://localhost:4000/api/tenants/realtime-dev/health"
]
timeout: 5s
interval: 5s
retries: 3
environment:
PORT: 4000
DB_HOST: ${POSTGRES_HOST}
DB_PORT: ${POSTGRES_PORT}
DB_USER: supabase_admin
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_NAME: ${POSTGRES_DB}
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
DB_ENC_KEY: supabaserealtime
API_JWT_SECRET: ${JWT_SECRET}
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
ERL_AFLAGS: -proto_dist inet_tcp
DNS_NODES: "''"
RLIMIT_NOFILE: "10000"
APP_NAME: realtime
SEED_SELF_HOST: true
RUN_JANITOR: true
# To use S3 backed storage: docker compose -f docker-compose.yml -f docker-compose.s3.yml up
storage:
container_name: supabase-storage
image: supabase/storage-api:v1.14.5
restart: unless-stopped
volumes:
- ./volumes/storage:/var/lib/storage:z
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://storage:5000/status"
]
timeout: 5s
interval: 5s
retries: 3
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
rest:
condition: service_started
imgproxy:
condition: service_started
environment:
ANON_KEY: ${ANON_KEY}
SERVICE_KEY: ${SERVICE_ROLE_KEY}
POSTGREST_URL: http://rest:3000
PGRST_JWT_SECRET: ${JWT_SECRET}
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
FILE_SIZE_LIMIT: 52428800
STORAGE_BACKEND: file
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
TENANT_ID: stub
# TODO: https://github.com/supabase/storage-api/issues/55
REGION: stub
GLOBAL_S3_BUCKET: stub
ENABLE_IMAGE_TRANSFORMATION: "true"
IMGPROXY_URL: http://imgproxy:5001
imgproxy:
container_name: supabase-imgproxy
image: darthsim/imgproxy:v3.8.0
restart: unless-stopped
volumes:
- ./volumes/storage:/var/lib/storage:z
healthcheck:
test:
[
"CMD",
"imgproxy",
"health"
]
timeout: 5s
interval: 5s
retries: 3
environment:
IMGPROXY_BIND: ":5001"
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
IMGPROXY_USE_ETAG: "true"
IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION}
meta:
container_name: supabase-meta
image: supabase/postgres-meta:v0.84.2
restart: unless-stopped
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
environment:
PG_META_PORT: 8080
PG_META_DB_HOST: ${POSTGRES_HOST}
PG_META_DB_PORT: ${POSTGRES_PORT}
PG_META_DB_NAME: ${POSTGRES_DB}
PG_META_DB_USER: supabase_admin
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
functions:
container_name: supabase-edge-functions
image: supabase/edge-runtime:v1.66.5
restart: unless-stopped
volumes:
- ./volumes/functions:/home/deno/functions:Z
depends_on:
analytics:
condition: service_healthy
environment:
JWT_SECRET: ${JWT_SECRET}
SUPABASE_URL: http://kong:8000
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
# TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786
VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}"
command:
[
"start",
"--main-service",
"/home/deno/functions/main"
]
analytics:
container_name: supabase-analytics
image: supabase/logflare:1.4.0
restart: unless-stopped
ports:
- 4000:4000
# Uncomment to use Big Query backend for analytics
# volumes:
# - type: bind
# source: ${PWD}/gcloud.json
# target: /opt/app/rel/logflare/bin/gcloud.json
# read_only: true
healthcheck:
test:
[
"CMD",
"curl",
"http://localhost:4000/health"
]
timeout: 5s
interval: 5s
retries: 10
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
environment:
LOGFLARE_NODE_HOST: 127.0.0.1
DB_USERNAME: supabase_admin
DB_DATABASE: _supabase
DB_HOSTNAME: ${POSTGRES_HOST}
DB_PORT: ${POSTGRES_PORT}
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_SCHEMA: _analytics
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
LOGFLARE_SINGLE_TENANT: true
LOGFLARE_SUPABASE_MODE: true
LOGFLARE_MIN_CLUSTER_SIZE: 1
# Comment variables to use Big Query backend for analytics
POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/_supabase
POSTGRES_BACKEND_SCHEMA: _analytics
LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true
# Uncomment to use Big Query backend for analytics
# GOOGLE_PROJECT_ID: ${GOOGLE_PROJECT_ID}
# GOOGLE_PROJECT_NUMBER: ${GOOGLE_PROJECT_NUMBER}
# Comment out everything below this point if you are using an external Postgres database
db:
container_name: supabase-db
image: supabase/postgres:15.8.1.020
restart: unless-stopped
volumes:
- ./volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z
# Must be superuser to create event trigger
- ./volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z
# Must be superuser to alter reserved role
- ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
# Initialize the database settings with JWT_SECRET and JWT_EXP
- ./volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z
# PGDATA directory is persisted between restarts
- ./volumes/db/data:/var/lib/postgresql/data:Z
# Changes required for internal supabase data such as _analytics
- ./volumes/db/_supabase.sql:/docker-entrypoint-initdb.d/migrations/97-_supabase.sql:Z
# Changes required for Analytics support
- ./volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z
# Changes required for Pooler support
- ./volumes/db/pooler.sql:/docker-entrypoint-initdb.d/migrations/99-pooler.sql:Z
# Use named volume to persist pgsodium decryption key between restarts
- db-config:/etc/postgresql-custom
healthcheck:
test:
[
"CMD",
"pg_isready",
"-U",
"postgres",
"-h",
"localhost"
]
interval: 5s
timeout: 5s
retries: 10
depends_on:
vector:
condition: service_healthy
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" # prevents Realtime polling queries from appearing in logs
]
vector:
container_name: supabase-vector
image: timberio/vector:0.28.1-alpine
restart: unless-stopped
volumes:
- ./volumes/logs/vector.yml:/etc/vector/vector.yml:ro
- ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://vector:9001/health"
]
timeout: 5s
interval: 5s
retries: 3
environment:
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
command:
[
"--config",
"/etc/vector/vector.yml"
]
# Update the DATABASE_URL if you are using an external Postgres database
supavisor:
container_name: supabase-pooler
image: supabase/supavisor:1.1.56
restart: unless-stopped
ports:
- ${POSTGRES_PORT}:5432
- ${POOLER_PROXY_PORT_TRANSACTION}:6543
volumes:
- ./volumes/pooler/pooler.exs:/etc/pooler/pooler.exs:ro
healthcheck:
test:
[
"CMD",
"curl",
"-sSfL",
"--head",
"-o",
"/dev/null",
"http://127.0.0.1:4000/api/health"
]
interval: 10s
timeout: 5s
retries: 5
depends_on:
db:
condition: service_healthy
analytics:
condition: service_healthy
environment:
PORT: 4000
POSTGRES_PORT: ${POSTGRES_PORT}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DATABASE_URL: ecto://supabase_admin:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/_supabase
CLUSTER_POSTGRES: true
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
VAULT_ENC_KEY: ${VAULT_ENC_KEY}
API_JWT_SECRET: ${JWT_SECRET}
METRICS_JWT_SECRET: ${JWT_SECRET}
REGION: local
ERL_AFLAGS: -proto_dist inet_tcp
POOLER_TENANT_ID: ${POOLER_TENANT_ID}
POOLER_DEFAULT_POOL_SIZE: ${POOLER_DEFAULT_POOL_SIZE}
POOLER_MAX_CLIENT_CONN: ${POOLER_MAX_CLIENT_CONN}
POOLER_POOL_MODE: transaction
command:
[
"/bin/sh",
"-c",
"/app/bin/migrate && /app/bin/supavisor eval \"$$(cat /etc/pooler/pooler.exs)\" && /app/bin/server"
]
volumes:
db-config:
+61
View File
@@ -0,0 +1,61 @@
services:
analytics:
labels:
- SERVICE_4000_CHECK_HTTP=/health
- SERVICE_4000_NAME=${COMPOSE_SERVICE_NAME}-analytics-4000
auth:
labels:
- SERVICE_9999_CHECK_HTTP=/health
- SERVICE_9999_NAME=${COMPOSE_SERVICE_NAME}-auth-9999
db:
labels:
- SERVICE_5432_CHECK_TCP=true
- SERVICE_5432_NAME=${COMPOSE_SERVICE_NAME}-db-5432
functions:
labels:
- SERVICE_9000_CHECK_HTTP=/functions/v1/hello
- SERVICE_9000_NAME=${COMPOSE_SERVICE_NAME}-functions-9000
imgproxy:
labels:
- SERVICE_5001_CHECK_HTTP=/health
- SERVICE_5001_NAME=${COMPOSE_SERVICE_NAME}-imgproxy-5001
- SERVICE_8080_IGNORE=true
kong:
labels:
- SERVICE_8000_CHECK_TCP=true
- SERVICE_8000_NAME=${COMPOSE_SERVICE_NAME}-kong-8000
- SERVICE_8000_TAGS=${SUPABASE_KONG_SERVICE_8000_TAGS:-urlprefix-supabase.localhost/*}
- SERVICE_8001_IGNORE=true
- SERVICE_8443_IGNORE=true
- SERVICE_8444_IGNORE=true
meta:
labels:
- SERVICE_8080_CHECK_HTTP=/
- SERVICE_8080_NAME=${COMPOSE_SERVICE_NAME}-meta-8080
rest:
labels:
- SERVICE_3000_CHECK_HTTP=/
- SERVICE_3000_NAME=${COMPOSE_SERVICE_NAME}-rest-3000
realtime:
labels:
- SERVICE_4000_CHECK_HTTP=/
- SERVICE_4000_NAME=${COMPOSE_SERVICE_NAME}-realtime-4000
storage:
labels:
- SERVICE_5000_CHECK_HTTP=/status
- SERVICE_5000_NAME=${COMPOSE_SERVICE_NAME}-storage-5000
studio:
labels:
- SERVICE_3000_CHECK_HTTP=/api/profile
- SERVICE_3000_NAME=${COMPOSE_SERVICE_NAME}-studio-3000
supavisor:
labels:
- SERVICE_4000_CHECK_HTTP=/api/health
- SERVICE_4000_NAME=${COMPOSE_SERVICE_NAME}-supavisor-4000
- SERVICE_5432_IGNORE=true
- SERVICE_6543_IGNORE=true
vector:
labels:
- SERVICE_9001_CHECK_HTTP=/health
- SERVICE_9001_NAME=${COMPOSE_SERVICE_NAME}-vector-9001
+9
View File
@@ -0,0 +1,9 @@
services:
supavisor:
ports:
- ${SUPABASE_POSTGRES_PORT:-5432}:5432/tcp
- ${SUPABASE_POOLER_PROXY_PORT_TRANSACTION:-6543}:6543/tcp
kong:
ports:
- ${SUPABASE_KONG_HTTP_PORT:-8000}:8000/tcp
- ${SUPABASE_KONG_HTTPS_PORT:-8443}:8443/tcp
+19
View File
@@ -0,0 +1,19 @@
ENV_VARS += SUPABASE_ADDITIONAL_REDIRECT_URLS SUPABASE_ANON_KEY SUPABASE_JWT_IAT SUPABASE_KONG_SERVICE_8000_TAGS SUPABASE_PUBLIC_URL SUPABASE_REALTIME_TENANT SUPABASE_SERVICE_ROLE_KEY SUPABASE_EDGE_RUNTIME_VERSION SUPABASE_KONG_VERSION SUPABASE_POSTGRES_VERSION SUPABASE_VECTOR_VERSION
SUPABASE_ADDITIONAL_REDIRECT_URLS ?= $(subst $(space),$(comma),$(call url,SUPABASE))
SUPABASE_ANON_KEY ?= $(call JWT,,$(SUPABASE_JWT_PAYLOAD_ANON),$(SUPABASE_JWT_SECRET))
SUPABASE_DOCKER_IMAGES ?= supabase/edge-runtime:$(SUPABASE_EDGE_RUNTIME_VERSION) kong:$(SUPABASE_KONG_VERSION) supabase/postgres:$(SUPABASE_POSTGRES_VERSION) timberio/vector:$(SUPABASE_VECTOR_VERSION)
SUPABASE_JWT_IAT ?= $(MAKE_UNIXTIME_START)
SUPABASE_JWT_EXP ?= $(shell echo $$(($(SUPABASE_JWT_IAT)+94608000)))
SUPABASE_JWT_PAYLOAD_ANON ?= {\n "role": "anon",\n "iss": "supabase",\n "iat": $(SUPABASE_JWT_IAT),\n "exp": $(SUPABASE_JWT_EXP)\n}
SUPABASE_JWT_PAYLOAD_SERVICE ?= {\n "role": "service_role",\n "iss": "supabase",\n "iat": $(SUPABASE_JWT_IAT),\n "exp": $(SUPABASE_JWT_EXP)\n}
SUPABASE_KONG_SERVICE_8000_TAGS ?= $(call tagprefix,SUPABASE,8000)
SUPABASE_PUBLIC_URL ?= $(SUPABASE_SERVICE_URL)
SUPABASE_REALTIME_TENANT ?= realtime-dev
SUPABASE_SERVICE_NAME ?= supabase
SUPABASE_SERVICE_ROLE_KEY ?= $(call JWT,,$(SUPABASE_JWT_PAYLOAD_SERVICE),$(SUPABASE_JWT_SECRET))
SUPABASE_SERVICE_URIS ?= $(call uri,SUPABASE)
SUPABASE_SERVICE_URL ?= $(call firstword,$(call url,SUPABASE))
SUPABASE_EDGE_RUNTIME_VERSION ?= $(shell awk -F: '/image: supabase\/edge-runtime:/ {print $$3}' stack/supabase/docker-compose.yml)
SUPABASE_KONG_VERSION ?= $(shell awk -F: '/image: kong:/ {print $$3}' stack/supabase/docker-compose.yml)
SUPABASE_POSTGRES_VERSION ?= $(shell awk -F: '/image: supabase\/postgres:/ {print $$3}' stack/supabase/docker-compose.yml)
SUPABASE_VECTOR_VERSION ?= $(shell awk -F: '/image: timberio\/vector:/ {print $$3}' stack/supabase/docker-compose.yml)
+31
View File
@@ -0,0 +1,31 @@
services:
db:
volumes: !override
- db-data:/var/lib/postgresql/data
- db-config:/etc/postgres-custom
functions:
volumes: !override
- functions-cache:/root/.cache
- functions-home:/home/deno/functions
imgproxy:
volumes: !override
- storage-data:/var/lib/storage
kong:
volumes: !override
- kong-home:/home/kong
storage:
volumes: !override
- storage-data:/var/lib/storage
vector:
volumes: !override
- vector-config:/etc/vector:ro
- ${DOCKER_SOCKET_LOCATION:-/var/run/docker.sock}:/var/run/docker.sock:ro
volumes:
db-config:
db-data:
functions-cache:
functions-home:
kong-home:
storage-data:
vector-config:
+272
View File
@@ -0,0 +1,272 @@
services:
studio:
container_name: !reset null
environment: !override
STUDIO_PG_META_URL: ${SUPABASE_STUDIO_PG_META_URL:-http://meta:8080}
POSTGRES_PASSWORD: ${SUPABASE_POSTGRES_PASSWORD}
DEFAULT_ORGANIZATION_NAME: ${SUPABASE_STUDIO_DEFAULT_ORGANIZATION:-Default Organization}
DEFAULT_PROJECT_NAME: ${SUPABASE_STUDIO_DEFAULT_PROJECT:-Default Project}
OPENAI_API_KEY: ${SUPABASE_OPENAI_API_KEY:-}
SUPABASE_URL: ${SUPABASE_URL:-http://kong:8000}
SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL:-http://localhost:8000}
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
AUTH_JWT_SECRET: ${SUPABASE_JWT_SECRET}
LOGFLARE_API_KEY: ${SUPABASE_LOGFLARE_API_KEY}
LOGFLARE_URL: ${SUPABASE_LOGFLARE_URL:-http://analytics:4000}
NEXT_PUBLIC_ENABLE_LOGS: ${SUPABASE_NEXT_PUBLIC_ENABLE_LOGS:-true}
NEXT_ANALYTICS_BACKEND_PROVIDER: ${SUPABASE_NEXT_ANALYTICS_BACKEND_PROVIDER:-postgres}
kong:
build:
args:
- DOCKER_BUILD_DIR=docker/supabase/kong
- DOCKER_NETWORK=${DOCKER_NETWORK_DEFAULT:-_default}
- REALTIME_TENANT=${SUPABASE_REALTIME_TENANT:-realtime-dev}
- SUPABASE_KONG_VERSION=${SUPABASE_KONG_VERSION:-2.8.1}
context: ../..
dockerfile: docker/supabase/kong/Dockerfile
container_name: !reset null
image: ${DOCKER_REPOSITORY}/kong:${DOCKER_IMAGE_TAG}
environment: !override
KONG_DATABASE: ${SUPABASE_KONG_DATABASE:-off}
KONG_DECLARATIVE_CONFIG: ${SUPABASE_KONG_DECLARATIVE_CONFIG:-/home/kong/kong.yml}
KONG_DNS_ORDER: ${SUPABASE_KONG_DNS_ORDER:-LAST,A,CNAME}
KONG_PLUGINS: ${SUPABASE_KONG_PLUGINS:-request-transformer,cors,key-auth,acl,basic-auth}
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: ${SUPABASE_KONG_NGINX_PROXY_PROXY_BUFFER_SIZE:-160k}
KONG_NGINX_PROXY_PROXY_BUFFERS: ${SUPABASE_KONG_NGINX_PROXY_PROXY_BUFFERS:-64 160k}
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
DASHBOARD_USERNAME: ${SUPABASE_DASHBOARD_USERNAME:-supabase}
DASHBOARD_PASSWORD: ${SUPABASE_DASHBOARD_PASSWORD}
ports: !override
- 8000/tcp
- 8443/tcp
auth:
container_name: !reset null
environment: !override
GOTRUE_API_HOST: ${SUPABASE_GOTRUE_API_HOST:-0.0.0.0}
GOTRUE_API_PORT: ${SUPABASE_GOTRUE_API_PORT:-9999}
API_EXTERNAL_URL: ${SUPABASE_API_EXTERNAL_URL:-${SUPABASE_PUBLIC_URL:-http://localhost:8000}}
GOTRUE_DB_DRIVER: ${SUPABASE_GOTRUE_DB_DRIVER:-postgres}
GOTRUE_DB_DATABASE_URL: postgres://${SUPABASE_POSTGRES_USER_AUTH:-supabase_auth_admin}:${SUPABASE_POSTGRES_PASSWORD_AUTH:-${SUPABASE_POSTGRES_PASSWORD}}@${SUPABASE_POSTGRES_HOST:-db}:${SUPABASE_POSTGRES_PORT:-5432}/${SUPABASE_POSTGRES_DB:-postgres}
GOTRUE_SITE_URL: ${SUPABASE_SITE_URL:-${SUPABASE_PUBLIC_URL:-http://localhost:3000}}
GOTRUE_URI_ALLOW_LIST: ${SUPABASE_ADDITIONAL_REDIRECT_URLS:-}
GOTRUE_DISABLE_SIGNUP: ${SUPABASE_DISABLE_SIGNUP:-false}
GOTRUE_JWT_ADMIN_ROLES: ${SUPABASE_JWT_ADMIN_ROLES:-service_role}
GOTRUE_JWT_AUD: ${SUPABASE_JWT_AUD:-authenticated}
GOTRUE_JWT_DEFAULT_GROUP_NAME: ${SUPABASE_JWT_DEFAULT_GROUP_NAME:-authenticated}
GOTRUE_JWT_EXP: ${SUPABASE_JWT_EXPIRY:-3600}
GOTRUE_JWT_SECRET: ${SUPABASE_JWT_SECRET}
GOTRUE_EXTERNAL_EMAIL_ENABLED: ${SUPABASE_ENABLE_EMAIL_SIGNUP:-true}
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${SUPABASE_ENABLE_ANONYMOUS_USERS:-false}
GOTRUE_MAILER_AUTOCONFIRM: ${SUPABASE_ENABLE_EMAIL_AUTOCONFIRM:-false}
GOTRUE_EXTERNAL_SKIP_NONCE_CHECK: ${SUPABASE_EXTERNAL_SKIP_NONCE_CHECK:-false}
GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: ${SUPABASE_MAILER_SECURE_EMAIL_CHANGE_ENABLED:-true}
GOTRUE_SMTP_MAX_FREQUENCY: ${SUPABASE_SMTP_MAX_FREQUENCY:-1s}
GOTRUE_SMTP_ADMIN_EMAIL: ${SUPABASE_SMTP_ADMIN_EMAIL:-}
GOTRUE_SMTP_HOST: ${SUPABASE_SMTP_HOST:-}
GOTRUE_SMTP_PORT: ${SUPABASE_SMTP_PORT:-25}
GOTRUE_SMTP_USER: ${SUPABASE_SMTP_USER:-}
GOTRUE_SMTP_PASS: ${SUPABASE_SMTP_PASS:-}
GOTRUE_SMTP_SENDER_NAME: ${SUPABASE_SMTP_SENDER_NAME:-}
GOTRUE_MAILER_URLPATHS_INVITE: ${SUPABASE_MAILER_URLPATHS_INVITE:-/auth/v1/verify}
GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${SUPABASE_MAILER_URLPATHS_CONFIRMATION:-/auth/v1/verify}
GOTRUE_MAILER_URLPATHS_RECOVERY: ${SUPABASE_MAILER_URLPATHS_RECOVERY:-/auth/v1/verify}
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${SUPABASE_MAILER_URLPATHS_EMAIL_CHANGE:-/auth/v1/verify}
GOTRUE_EXTERNAL_PHONE_ENABLED: ${SUPABASE_ENABLE_PHONE_SIGNUP:-true}
GOTRUE_SMS_AUTOCONFIRM: ${SUPABASE_ENABLE_PHONE_AUTOCONFIRM:-true}
GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED: ${SUPABASE_GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED:-false}
GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI: ${SUPABASE_GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI:-}
GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS: ${SUPABASE_GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS:-}
GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED: ${SUPABASE_GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED:-false}
GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI: ${SUPABASE_GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI:-}
GOTRUE_HOOK_SEND_SMS_ENABLED: ${SUPABASE_GOTRUE_HOOK_SEND_SMS_ENABLED:-false}
GOTRUE_HOOK_SEND_SMS_URI: ${SUPABASE_GOTRUE_HOOK_SEND_SMS_URI:-}
GOTRUE_HOOK_SEND_SMS_SECRETS: ${SUPABASE_GOTRUE_HOOK_SEND_SMS_SECRETS:-}
GOTRUE_HOOK_SEND_EMAIL_ENABLED: ${SUPABASE_GOTRUE_HOOK_SEND_EMAIL_ENABLED:-false}
GOTRUE_HOOK_SEND_EMAIL_URI: ${SUPABASE_GOTRUE_HOOK_SEND_EMAIL_URI:-}
GOTRUE_HOOK_SEND_EMAIL_SECRETS: ${SUPABASE_GOTRUE_HOOK_SEND_EMAIL_SECRETS:-}
rest:
container_name: !reset null
environment: !override
PGRST_DB_URI: postgres://${SUPABASE_POSTGRES_USER_REST:-authenticator}:${SUPABASE_POSTGRES_PASSWORD_REST:-${SUPABASE_POSTGRES_PASSWORD}}@${SUPABASE_POSTGRES_HOST:-db}:${SUPABASE_POSTGRES_PORT:-5432}/${SUPABASE_POSTGRES_DB:-postgres}
PGRST_DB_SCHEMAS: ${SUPABASE_PGRST_DB_SCHEMAS:-public,storage,graphql_public}
PGRST_DB_ANON_ROLE: ${SUPABASE_PGRST_DB_ANON_ROLE:-anon}
PGRST_JWT_SECRET: ${SUPABASE_JWT_SECRET}
PGRST_DB_USE_LEGACY_GUCS: ${SUPABASE_PGRST_DB_USE_LEGACY_GUCS:-false}
PGRST_APP_SETTINGS_JWT_SECRET: ${SUPABASE_JWT_SECRET}
PGRST_APP_SETTINGS_JWT_EXP: ${SUPABASE_JWT_EXPIRY:-3600}
realtime:
container_name: !reset null
environment: !override
PORT: ${SUPABASE_REALTIME_PORT:-4000}
DB_HOST: ${SUPABASE_POSTGRES_HOST:-db}
DB_PORT: ${SUPABASE_POSTGRES_PORT:-5432}
DB_USER: ${SUPABASE_POSTGRES_USER_SUPABASE:-supabase_admin}
DB_PASSWORD: ${SUPABASE_POSTGRES_PASSWORD_SUPABASE:-${SUPABASE_POSTGRES_PASSWORD}}
DB_NAME: ${SUPABASE_POSTGRES_DB:-postgres}
DB_AFTER_CONNECT_QUERY: ${SUPABASE_POSTGRES_DB_AFTER_CONNECT_QUERY:-SET search_path TO _realtime}
DB_ENC_KEY: ${SUPABASE_POSTGRES_DB_ENC_KEY:-supabaserealtime}
API_JWT_SECRET: ${SUPABASE_JWT_SECRET}
SECRET_KEY_BASE: ${SUPABASE_SECRET_KEY_BASE}
ERL_AFLAGS: ${SUPABASE_REALTIME_ERL_AFLAGS:--proto_dist inet_tcp}
DNS_NODES: ${SUPABASE_REALTIME_DNS_NODES:-''}
RLIMIT_NOFILE: ${SUPABASE_REALTIME_RLIMIT_NOFILE:-10000}
APP_NAME: ${SUPABE_REALTIME_APP_NAME:-realtime}
SEED_SELF_HOST: ${SUPABASE_REALTIME_SEED_SELF_HOST:-true}
RUN_JANITOR: ${SUPABASE_REALTIME_RUN_JANITOR:-true}
SELF_HOST_TENANT_NAME: ${SUPABASE_REALTIME_TENANT:-realtime-dev}
MIX_ENV: ${SUPABASE_REALTIME_MIX_ENV:-prod}
healthcheck:
test:
[
"CMD",
"curl",
"-sSfL",
"--head",
"-o",
"/dev/null",
"-H",
"Authorization: Bearer ${SUPABASE_ANON_KEY}",
"http://localhost:4000/api/tenants/${SUPABASE_REALTIME_TENANT:-realtime-dev}/health"
]
timeout: 5s
interval: 5s
retries: 3
networks:
default:
aliases:
- ${SUPABASE_REALTIME_TENANT:-realtime-dev}
storage:
container_name: !reset null
environment: !override
ANON_KEY: ${SUPABASE_ANON_KEY}
SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
POSTGREST_URL: ${SUPABASE_POSTGREST_URL:-http://rest:3000}
PGRST_JWT_SECRET: ${SUPABASE_JWT_SECRET}
DATABASE_URL: postgres://${SUPABASE_POSTGRES_USER_STORAGE:-supabase_storage_admin}:${SUPABASE_POSTGRES_PASSWORD_STORAGE:-${SUPABASE_POSTGRES_PASSWORD}}@${SUPABASE_POSTGRES_HOST:-db}:${SUPABASE_POSTGRES_PORT:-5432}/${SUPABASE_POSTGRES_DB:-postgres}
FILE_SIZE_LIMIT: ${SUPABASE_STORAGE_FILE_SIZE_LIMIT:-52428800}
STORAGE_BACKEND: ${SUPABASE_STORAGE_BACKEND:-file}
FILE_STORAGE_BACKEND_PATH: ${SUPABASE_FILE_STORAGE_BACKEND_PATH:-/var/lib/storage}
TENANT_ID: ${SUPABASE_STORAGE_TENANT_ID:-stub}
REGION: ${SUPABASE_STORAGE_REGION:-stub}
GLOBAL_S3_BUCKET: ${SUPABASE_STORAGE_GLOBAL_S3_BUCKET:-stub}
ENABLE_IMAGE_TRANSFORMATION: ${SUPABASE_STORAGE_ENABLE_IMAGE_TRANSFORMATION:-true}
IMGPROXY_URL: ${SUPABASE_IMGPROXY_URL:-http://imgproxy:5001}
imgproxy:
container_name: !reset null
environment: !override
IMGPROXY_BIND: ${SUPABASE_IMGPROXY_BIND:-:5001}
IMGPROXY_LOCAL_FILESYSTEM_ROOT: ${SUPABASE_IMGPROXY_LOCAL_FILESYSTEM_ROOT:-/}
IMGPROXY_USE_ETAG: ${SUPABASE_IMGPROXY_USE_ETAG:-true}
IMGPROXY_ENABLE_WEBP_DETECTION: ${SUPABASE_IMGPROXY_ENABLE_WEBP_DETECTION:-true}
meta:
container_name: !reset null
environment: !override
PG_META_PORT: ${SUPABASE_META_PORT:-8080}
PG_META_DB_HOST: ${SUPABASE_POSTGRES_HOST:-db}
PG_META_DB_PORT: ${SUPABASE_POSTGRES_PORT:-5432}
PG_META_DB_NAME: ${SUPABASE_POSTGRES_DB:-postgres}
PG_META_DB_USER: ${SUPABASE_POSTGRES_USER_SUPABASE:-supabase_admin}
PG_META_DB_PASSWORD: ${SUPABASE_POSTGRES_PASSWORD_SUPABASE:-${SUPABASE_POSTGRES_PASSWORD}}
functions:
build:
args:
- DOCKER_BUILD_DIR=docker/supabase/edge-runtime
- SUPABASE_EDGE_RUNTIME_VERSION=${SUPABASE_EDGE_RUNTIME_VERSION:-v1.65.3}
context: ../..
dockerfile: docker/supabase/edge-runtime/Dockerfile
container_name: !reset null
image: ${DOCKER_REPOSITORY}/edge-runtime:${DOCKER_IMAGE_TAG}
environment: !override
JWT_SECRET: ${SUPABASE_JWT_SECRET}
SUPABASE_URL: ${SUPABASE_URL:-http://kong:8000}
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
SUPABASE_DB_URL: postgresql://${SUPABASE_POSTGRES_USER:-postgres}:${SUPABASE_POSTGRES_PASSWORD}@${SUPABASE_POSTGRES_HOST:-db}:${SUPABASE_POSTGRES_PORT:-5432}/${SUPABASE_POSTGRES_DB:-postgres}
VERIFY_JWT: "${SUPABASE_FUNCTIONS_VERIFY_JWT}"
analytics:
container_name: !reset null
environment: !override
LOGFLARE_NODE_HOST: ${SUPABASE_LOGFLARE_NODE_HOST:-127.0.0.1}
DB_USERNAME: ${SUPABASE_POSTGRES_USER_SUPABASE:-supabase_admin}
DB_DATABASE: ${SUPABASE_POSTGRES_DB_SUPABASE:-_supabase}
DB_HOSTNAME: ${SUPABASE_POSTGRES_HOST:-db}
DB_PORT: ${SUPABASE_POSTGRES_PORT:-5432}
DB_PASSWORD: ${SUPABASE_POSTGRES_PASSWORD_SUPABASE:-${SUPABASE_POSTGRES_PASSWORD}}
DB_SCHEMA: ${SUPABASE_POSTGRES_SCHEMA_SUPABASE:-_analytics}
LOGFLARE_API_KEY: ${SUPABASE_LOGFLARE_API_KEY}
LOGFLARE_SINGLE_TENANT: ${SUPABASE_LOGFLARE_SINGLE_TENANT:-true}
LOGFLARE_SUPABASE_MODE: ${SUPABASE_LOGFLARE_SUPABASE_MODE:-true}
LOGFLARE_MIN_CLUSTER_SIZE: ${SUPABASE_LOGFLARE_MIN_CLUSTER_SIZE:-1}
POSTGRES_BACKEND_URL: postgresql://${SUPABASE_POSTGRES_USER_SUPABASE:-supabase_admin}:${SUPABASE_POSTGRES_PASSWORD_SUPABASE:-${SUPABASE_POSTGRES_PASSWORD}}@${SUPABASE_POSTGRES_HOST:-db}:${SUPABASE_POSTGRES_PORT:-5432}/${SUPABASE_POSTGRES_DB_SUPABASE:-_supabase}
POSTGRES_BACKEND_SCHEMA: ${SUPABASE_POSTGRES_SCHEMA_SUPABASE:-_analytics}
LOGFLARE_FEATURE_FLAG_OVERRIDE: ${SUPABASE_LOGFLARE_FEATURE_FLAG_OVERRIDE:-multibackend=true}
GOOGLE_PROJECT_ID: ${SUPABASE_GOOGLE_PROJECT_ID:-}
GOOGLE_PROJECT_NUMBER: ${SUPABASE_GOOGLE_PROJECT_NUMBER:-}
ports: !override
- 4000
db:
build:
args:
- DOCKER_BUILD_DIR=docker/supabase/postgres
- SUPABASE_POSTGRES_VERSION=${SUPABASE_POSTGRES_VERSION:-15.6.1.146}
context: ../..
dockerfile: docker/supabase/postgres/Dockerfile
container_name: !reset null
image: ${DOCKER_REPOSITORY}/postgres:${DOCKER_IMAGE_TAG}
environment: !override
POSTGRES_HOST: /var/run/postgresql
PGPORT: ${SUPABASE_POSTGRES_PORT:-5432}
POSTGRES_PORT: ${SUPABASE_POSTGRES_PORT:-5432}
PGPASSWORD: ${SUPABASE_POSTGRES_PASSWORD}
POSTGRES_PASSWORD: ${SUPABASE_POSTGRES_PASSWORD}
PGDATABASE: ${SUPABASE_POSTGRES_DB:-postgres}
POSTGRES_DB: ${SUPABASE_POSTGRES_DB:-postgres}
JWT_SECRET: ${SUPABASE_JWT_SECRET}
JWT_EXP: ${SUPABASE_JWT_EXPIRY:-3600}
vector:
build:
args:
- DOCKER_BUILD_DIR=docker/supabase/vector
- SUPABASE_VECTOR_VERSION=${SUPABASE_VECTOR_VERSION:-0.28.1-alpine}
context: ../..
dockerfile: docker/supabase/vector/Dockerfile
container_name: !reset null
image: ${DOCKER_REPOSITORY}/vector:${DOCKER_IMAGE_TAG}
environment: !override
LOGFLARE_API_KEY: ${SUPABASE_LOGFLARE_API_KEY}
supavisor:
container_name: !reset null
ports: !override
- 5432
- 6543
environment: !override
- PORT=${SUPABASE_SUPAVISOR_PORT:-4000}
- POSTGRES_PORT=${SUPABASE_POSTGRES_PORT:-5432}
- POSTGRES_DB=${SUPABASE_POSTGRES_DB:-postgres}
- POSTGRES_PASSWORD=${SUPABASE_POSTGRES_PASSWORD}
- DATABASE_URL=ecto://${SUPABASE_POSTGRES_USER_SUPABASE:-supabase_admin}:${SUPABASE_POSTGRES_PASSWORD_SUPABASE:-${SUPABASE_POSTGRES_PASSWORD}}@${SUPABASE_POSTGRES_HOST:-db}:${SUPABASE_POSTGRES_PORT:-5432}/${SUPABASE_POSTGRES_DB_SUPABASE:-_supabase}
- CLUSTER_POSTGRES=true
- SECRET_KEY_BASE=${SUPABASE_SECRET_KEY_BASE}
- VAULT_ENC_KEY=${SUPABASE_VAULT_ENC_KEY}
- API_JWT_SECRET=${SUPABASE_JWT_SECRET}
- METRICS_JWT_SECRET=${SUPABASE_JWT_SECRET}
- REGION=${SUPABASE_SUPAVISOR_REGION:-local}
- ERL_AFLAGS=${SUPABASE_SUPAVISOR_ERL_AFLAGS:--proto_dist inet_tcp}
- POOLER_TENANT_ID=${SUPABASE_POOLER_TENANT_ID:-local}
- POOLER_DEFAULT_POOL_SIZE=${SUPABASE_POOLER_DEFAULT_POOL_SIZE:-20}
- POOLER_MAX_CLIENT_CONN=${SUPABASE_POOLER_MAX_CLIENT_CONN:-100}
- POOLER_POOL_MODE=${SUPABASE_POOLER_POOL_MODE:-transaction}
+241
View File
@@ -0,0 +1,241 @@
_format_version: '2.1'
_transform: true
###
### Consumers / Users
###
consumers:
- username: DASHBOARD
- username: anon
keyauth_credentials:
- key: $SUPABASE_ANON_KEY
- username: service_role
keyauth_credentials:
- key: $SUPABASE_SERVICE_KEY
###
### Access Control List
###
acls:
- consumer: anon
group: anon
- consumer: service_role
group: admin
###
### Dashboard credentials
###
basicauth_credentials:
- consumer: DASHBOARD
username: $DASHBOARD_USERNAME
password: $DASHBOARD_PASSWORD
###
### API Routes
###
services:
## Open Auth routes
- 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
## Secure Auth routes
- name: auth-v1
_comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*'
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
## Secure REST routes
- name: rest-v1
_comment: 'PostgREST: /rest/v1/* -> http://rest:3000/*'
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
## Secure GraphQL routes
- name: graphql-v1
_comment: 'PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql'
url: http://rest:3000/rpc/graphql
routes:
- name: graphql-v1-all
strip_path: true
paths:
- /graphql/v1
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
- name: request-transformer
config:
add:
headers:
- Content-Profile:graphql_public
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Secure Realtime routes
- name: realtime-v1-ws
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
url: http://realtime-dev.supabase-realtime:4000/socket
protocol: ws
routes:
- name: realtime-v1-ws
strip_path: true
paths:
- /realtime/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: realtime-v1-rest
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
url: http://realtime-dev.supabase-realtime:4000/api
protocol: http
routes:
- name: realtime-v1-rest
strip_path: true
paths:
- /realtime/v1/api
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Storage routes: the storage server manages its own auth
- name: storage-v1
_comment: 'Storage: /storage/v1/* -> http://storage:5000/*'
url: http://storage:5000/
routes:
- name: storage-v1-all
strip_path: true
paths:
- /storage/v1/
plugins:
- name: cors
## Edge Functions routes
- name: functions-v1
_comment: 'Edge Functions: /functions/v1/* -> http://functions:9000/*'
url: http://functions:9000/
routes:
- name: functions-v1-all
strip_path: true
paths:
- /functions/v1/
plugins:
- name: cors
## Analytics routes
- name: analytics-v1
_comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*'
url: http://analytics:4000/
routes:
- name: analytics-v1-all
strip_path: true
paths:
- /analytics/v1/
## Secure Database routes
- name: meta
_comment: 'pg-meta: /pg/* -> http://pg-meta:8080/*'
url: http://meta:8080/
routes:
- name: meta-all
strip_path: true
paths:
- /pg/
plugins:
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
## Protected Dashboard - catch all remaining routes
- name: dashboard
_comment: 'Studio: /* -> http://studio:3000/*'
url: http://studio:3000/
routes:
- name: dashboard-all
strip_path: true
paths:
- /
plugins:
- name: cors
- name: basic-auth
config:
hide_credentials: true
+3
View File
@@ -0,0 +1,3 @@
\set pguser `echo "$POSTGRES_USER"`
CREATE DATABASE _supabase WITH OWNER :pguser;
View File
+5
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';
+6
View File
@@ -0,0 +1,6 @@
\set pguser `echo "$POSTGRES_USER"`
\c _supabase
create schema if not exists _analytics;
alter schema _analytics owner to :pguser;
\c postgres
+6
View File
@@ -0,0 +1,6 @@
\set pguser `echo "$POSTGRES_USER"`
\c _supabase
create schema if not exists _supavisor;
alter schema _supavisor owner to :pguser;
\c postgres
+4
View File
@@ -0,0 +1,4 @@
\set pguser `echo "$POSTGRES_USER"`
create schema if not exists _realtime;
alter schema _realtime owner to :pguser;
+8
View File
@@ -0,0 +1,8 @@
-- 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_functions_admin WITH PASSWORD :'pgpass';
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
+208
View File
@@ -0,0 +1,208 @@
BEGIN;
-- Create pg_net extension
CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
-- Create supabase_functions schema
CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
-- supabase_functions.migrations definition
CREATE TABLE supabase_functions.migrations (
version text PRIMARY KEY,
inserted_at timestamptz NOT NULL DEFAULT NOW()
);
-- Initial supabase_functions migration
INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
-- supabase_functions.hooks definition
CREATE TABLE supabase_functions.hooks (
id bigserial PRIMARY KEY,
hook_table_id integer NOT NULL,
hook_name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
request_id bigint
);
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
CREATE FUNCTION supabase_functions.http_request()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
request_id bigint;
payload jsonb;
url text := TG_ARGV[0]::text;
method text := TG_ARGV[1]::text;
headers jsonb DEFAULT '{}'::jsonb;
params jsonb DEFAULT '{}'::jsonb;
timeout_ms integer DEFAULT 1000;
BEGIN
IF url IS NULL OR url = 'null' THEN
RAISE EXCEPTION 'url argument is missing';
END IF;
IF method IS NULL OR method = 'null' THEN
RAISE EXCEPTION 'method argument is missing';
END IF;
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
headers = '{"Content-Type": "application/json"}'::jsonb;
ELSE
headers = TG_ARGV[2]::jsonb;
END IF;
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
params = '{}'::jsonb;
ELSE
params = TG_ARGV[3]::jsonb;
END IF;
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
timeout_ms = 1000;
ELSE
timeout_ms = TG_ARGV[4]::integer;
END IF;
CASE
WHEN method = 'GET' THEN
SELECT http_get INTO request_id FROM net.http_get(
url,
params,
headers,
timeout_ms
);
WHEN method = 'POST' THEN
payload = jsonb_build_object(
'old_record', OLD,
'record', NEW,
'type', TG_OP,
'table', TG_TABLE_NAME,
'schema', TG_TABLE_SCHEMA
);
SELECT http_post INTO request_id FROM net.http_post(
url,
payload,
params,
headers,
timeout_ms
);
ELSE
RAISE EXCEPTION 'method argument % is invalid', method;
END CASE;
INSERT INTO supabase_functions.hooks
(hook_table_id, hook_name, request_id)
VALUES
(TG_RELID, TG_NAME, request_id);
RETURN NEW;
END
$function$;
-- Supabase super admin
DO
$$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_roles
WHERE rolname = 'supabase_functions_admin'
)
THEN
CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
END IF;
END
$$;
GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
GRANT supabase_functions_admin TO postgres;
-- Remove unused supabase_pg_net_admin role
DO
$$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_roles
WHERE rolname = 'supabase_pg_net_admin'
)
THEN
REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
DROP OWNED BY supabase_pg_net_admin;
DROP ROLE supabase_pg_net_admin;
END IF;
END
$$;
-- pg_net grants when extension is already enabled
DO
$$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_extension
WHERE extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END
$$;
-- Event trigger for pg_net
CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_event_trigger_ddl_commands() AS ev
JOIN pg_extension AS ext
ON ev.objid = ext.oid
WHERE ext.extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END;
$$;
COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
DO
$$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_event_trigger
WHERE evtname = 'issue_pg_net_access'
) THEN
CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
EXECUTE PROCEDURE extensions.grant_pg_net_access();
END IF;
END
$$;
INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
ALTER function supabase_functions.http_request() SECURITY DEFINER;
ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
COMMIT;
@@ -0,0 +1,16 @@
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.177.1/http/server.ts"
serve(async () => {
return new Response(
`"Hello from Edge Functions!"`,
{ headers: { "Content-Type": "application/json" } },
)
})
// To invoke:
// curl 'http://localhost:<KONG_HTTP_PORT>/functions/v1/hello' \
// --header 'Authorization: Bearer <anon/service_role API key>'
@@ -0,0 +1,94 @@
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
console.log('main function started')
const JWT_SECRET = Deno.env.get('JWT_SECRET')
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
function getAuthToken(req: Request) {
const authHeader = req.headers.get('authorization')
if (!authHeader) {
throw new Error('Missing authorization header')
}
const [bearer, token] = authHeader.split(' ')
if (bearer !== 'Bearer') {
throw new Error(`Auth header is not 'Bearer {token}'`)
}
return token
}
async function verifyJWT(jwt: string): Promise<boolean> {
const encoder = new TextEncoder()
const secretKey = encoder.encode(JWT_SECRET)
try {
await jose.jwtVerify(jwt, secretKey)
} catch (err) {
console.error(err)
return false
}
return true
}
serve(async (req: Request) => {
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
try {
const token = getAuthToken(req)
const isValidJWT = await verifyJWT(token)
if (!isValidJWT) {
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
} catch (e) {
console.error(e)
return new Response(JSON.stringify({ msg: e.toString() }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
}
const url = new URL(req.url)
const { pathname } = url
const path_parts = pathname.split('/')
const service_name = path_parts[1]
if (!service_name || service_name === '') {
const error = { msg: 'missing function name in request' }
return new Response(JSON.stringify(error), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const servicePath = `/home/deno/functions/${service_name}`
console.error(`serving the request with ${servicePath}`)
const memoryLimitMb = 150
const workerTimeoutMs = 1 * 60 * 1000
const noModuleCache = false
const importMapPath = null
const envVarsObj = Deno.env.toObject()
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
try {
const worker = await EdgeRuntime.userWorkers.create({
servicePath,
memoryLimitMb,
workerTimeoutMs,
noModuleCache,
importMapPath,
envVars,
})
return await worker.fetch(req)
} catch (e) {
const error = { msg: e.toString() }
return new Response(JSON.stringify(error), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
})
+232
View File
@@ -0,0 +1,232 @@
api:
enabled: true
address: 0.0.0.0:9001
sources:
docker_host:
type: docker_logs
exclude_containers:
- supabase-vector
transforms:
project_logs:
type: remap
inputs:
- docker_host
source: |-
.project = "default"
.event_message = del(.message)
.appname = del(.container_name)
del(.container_created_at)
del(.container_id)
del(.source_type)
del(.stream)
del(.label)
del(.image)
del(.host)
del(.stream)
router:
type: route
inputs:
- project_logs
route:
kong: '.appname == "supabase-kong"'
auth: '.appname == "supabase-auth"'
rest: '.appname == "supabase-rest"'
realtime: '.appname == "supabase-realtime"'
storage: '.appname == "supabase-storage"'
functions: '.appname == "supabase-functions"'
db: '.appname == "supabase-db"'
# Ignores non nginx errors since they are related with kong booting up
kong_logs:
type: remap
inputs:
- router.kong
source: |-
req, err = parse_nginx_log(.event_message, "combined")
if err == null {
.timestamp = req.timestamp
.metadata.request.headers.referer = req.referer
.metadata.request.headers.user_agent = req.agent
.metadata.request.headers.cf_connecting_ip = req.client
.metadata.request.method = req.method
.metadata.request.path = req.path
.metadata.request.protocol = req.protocol
.metadata.response.status_code = req.status
}
if err != null {
abort
}
# Ignores non nginx errors since they are related with kong booting up
kong_err:
type: remap
inputs:
- router.kong
source: |-
.metadata.request.method = "GET"
.metadata.response.status_code = 200
parsed, err = parse_nginx_log(.event_message, "error")
if err == null {
.timestamp = parsed.timestamp
.severity = parsed.severity
.metadata.request.host = parsed.host
.metadata.request.headers.cf_connecting_ip = parsed.client
url, err = split(parsed.request, " ")
if err == null {
.metadata.request.method = url[0]
.metadata.request.path = url[1]
.metadata.request.protocol = url[2]
}
}
if err != null {
abort
}
# Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.
auth_logs:
type: remap
inputs:
- router.auth
source: |-
parsed, err = parse_json(.event_message)
if err == null {
.metadata.timestamp = parsed.time
.metadata = merge!(.metadata, parsed)
}
# PostgREST logs are structured so we separate timestamp from message using regex
rest_logs:
type: remap
inputs:
- router.rest
source: |-
parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.timestamp = to_timestamp!(parsed.time)
.metadata.host = .project
}
# Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date)
realtime_logs:
type: remap
inputs:
- router.realtime
source: |-
.metadata.project = del(.project)
.metadata.external_id = .metadata.project
parsed, err = parse_regex(.event_message, r'^(?P<time>\d+:\d+:\d+\.\d+) \[(?P<level>\w+)\] (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
}
# Storage logs may contain json objects so we parse them for completeness
storage_logs:
type: remap
inputs:
- router.storage
source: |-
.metadata.project = del(.project)
.metadata.tenantId = .metadata.project
parsed, err = parse_json(.event_message)
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
.metadata.timestamp = parsed.time
.metadata.context[0].host = parsed.hostname
.metadata.context[0].pid = parsed.pid
}
# Postgres logs some messages to stderr which we map to warning severity level
db_logs:
type: remap
inputs:
- router.db
source: |-
.metadata.host = "db-default"
.metadata.parsed.timestamp = .timestamp
parsed, err = parse_regex(.event_message, r'.*(?P<level>INFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)
if err != null || parsed == null {
.metadata.parsed.error_severity = "info"
}
if parsed != null {
.metadata.parsed.error_severity = parsed.level
}
if .metadata.parsed.error_severity == "info" {
.metadata.parsed.error_severity = "log"
}
.metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)
sinks:
logflare_auth:
type: 'http'
inputs:
- auth_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=gotrue.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_realtime:
type: 'http'
inputs:
- realtime_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=realtime.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_rest:
type: 'http'
inputs:
- rest_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=postgREST.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_db:
type: 'http'
inputs:
- db_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
# We must route the sink through kong because ingesting logs before logflare is fully initialised will
# lead to broken queries from studio. This works by the assumption that containers are started in the
# following order: vector > db > logflare > kong
uri: 'http://kong:8000/analytics/v1/api/logs?source_name=postgres.logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_functions:
type: 'http'
inputs:
- router.functions
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=deno-relay-logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_storage:
type: 'http'
inputs:
- storage_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=storage.logs.prod.2&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_kong:
type: 'http'
inputs:
- kong_logs
- kong_err
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=cloudflare.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+30
View File
@@ -0,0 +1,30 @@
{:ok, _} = Application.ensure_all_started(:supavisor)
{:ok, version} =
case Supavisor.Repo.query!("select version()") do
%{rows: [[ver]]} -> Supavisor.Helpers.parse_pg_version(ver)
_ -> nil
end
params = %{
"external_id" => System.get_env("POOLER_TENANT_ID"),
"db_host" => "db",
"db_port" => System.get_env("POSTGRES_PORT"),
"db_database" => System.get_env("POSTGRES_DB"),
"require_user" => false,
"auth_query" => "SELECT * FROM pgbouncer.get_auth($1)",
"default_max_clients" => System.get_env("POOLER_MAX_CLIENT_CONN"),
"default_pool_size" => System.get_env("POOLER_DEFAULT_POOL_SIZE"),
"default_parameter_status" => %{"server_version" => version},
"users" => [%{
"db_user" => "pgbouncer",
"db_password" => System.get_env("POSTGRES_PASSWORD"),
"mode_type" => System.get_env("POOLER_POOL_MODE"),
"pool_size" => System.get_env("POOLER_DEFAULT_POOL_SIZE"),
"is_manager" => true
}]
}
if !Supavisor.Tenants.get_tenant_by_external_id(params["external_id"]) do
{:ok, _} = Supavisor.Tenants.create_tenant(params)
end
+1 -1
View File
@@ -33,7 +33,7 @@ services:
volumes: volumes:
- sources:/Sources:cached - sources:/Sources:cached
- user:/tmp/ssh-agent:ro - user:/tmp/ssh-agent:ro
- /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock
volumes: volumes:
sources: sources: