build: smaller docker images

This commit is contained in:
sylv 2022-07-05 18:39:30 +08:00
parent 5b325e595c
commit 7e50fb043b
20 changed files with 1522 additions and 1100 deletions

View File

@ -1,24 +1,19 @@
*
.env
*.env
.microrc.yaml
.secrets
!example
!packages
Dockerfile
node_modules
example
data
.vscode
.github
*.md
!LICENSE
!package.json
!tsconfig.json
!tsconfig.server.json
!tailwind.config.js
!next-env.d.ts
!next.config.js
!postcss.config.js
!pnpm-lock.yaml
!pnpm-workspace.yaml
!tsup.config.ts
!wrapper.sh
!turbo.json
packages/api/data
packages/api/dist
packages/web/.next
packages/*/.turbo
packages/*/.turbo
packages/*/data
packages/*/node_modules
packages/*/dist
packages/*/.next
packages/*/.swc

View File

@ -1,12 +0,0 @@
module.exports = {
hooks: {
readPackage: (pkg, context) => {
if (pkg.name === "@mikro-orm/cli") {
delete pkg.dependencies["@mikro-orm/core"];
pkg.peerDependencies["@mikro-orm/core"] = pkg.peerDependencies["@mikro-orm/postgresql"];
}
return pkg;
},
},
};

View File

@ -1,32 +1,56 @@
FROM node:16-slim AS builder
RUN npm i -g pnpm@7 tsup
ENV NODE_ENV=development
FROM node:16-alpine AS deps
ENV NEXT_TELEMETRY_DISABLED 1
RUN apk add --no-cache libc6-compat
RUN npm i -g pnpm
# install development dependencies
WORKDIR /usr/src/micro
RUN apt update && apt install -y ffmpeg git
# copy package.jsons and install dependencies
# doing this before copying everything helps with caching
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .
COPY packages/web/package.json packages/web/package.json
COPY packages/thumbnail-generator/package.json packages/thumbnail-generator/package.json
COPY packages/api/package.json packages/api/package.json
RUN pnpm install --frozen-lockfile
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm fetch
# copy sources and build app
FROM node:16-alpine AS builder
ENV NEXT_TELEMETRY_DISABLED 1
WORKDIR /usr/src/micro
RUN apk add --no-cache git
RUN npm i -g pnpm
COPY --from=deps /usr/src/micro .
COPY . .
RUN pnpm install --offline --frozen-lockfile
RUN pnpm build
# prune unused packages
# RUN pnpm prune --prod
# run as the "node" user https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
FROM node:16-alpine AS runner
ENV NEXT_TELEMETRY_DISABLED 1
ENV NODE_ENV production
WORKDIR /usr/src/micro
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# copy file dependencies
COPY --from=builder /usr/src/micro/packages/web/public ./packages/web/public
COPY --from=builder /usr/src/micro/packages/web/next.config.js ./packages/web/next.config.js
# copy web server
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/web/.next/standalone/ ./
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/web/.next/static ./packages/web/.next/static/
# copy api
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/api/dist ./packages/api/dist
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/api/dist ./packages/api/dist
COPY wrapper.sh .
RUN chmod +x ./wrapper.sh
RUN chown node:node packages/api/src/schema.gql
USER node
ENV NODE_ENV=production
# define how we want to start the app
ENTRYPOINT ["./wrapper.sh"]

View File

@ -17,6 +17,6 @@
"clean": "rm -rf ./packages/*/{tsconfig.tsbuildinfo,lib,dist,yarn-error.log,.next}"
},
"devDependencies": {
"turbo": "^1.3.0"
"turbo": "1.3.2-canary.0"
}
}

View File

@ -10,9 +10,8 @@
},
"scripts": {
"watch": "tsup src/main.ts src/migrations/* --watch --onSuccess \"node dist/main.js\" --target node16",
"build": "rm -rf ./dist && tsup src/main.ts src/migrations/* --dts --sourcemap --target node16",
"build": "rm -rf ./dist && ncc build src/main.ts -o dist --minify --transpile-only --v8-cache --no-source-map-register",
"lint": "eslint src --fix --cache",
"start": "node ./dist/main.js",
"test": "jest"
},
"dependencies": {
@ -20,10 +19,10 @@
"@fastify/helmet": "^8.0.0",
"@fastify/multipart": "^6.0.0",
"@jenyus-org/nestjs-graphql-utils": "^1.6.4",
"@mikro-orm/core": "^5.2.0",
"@mikro-orm/migrations": "^5.2.0",
"@mikro-orm/core": "^5.2.2",
"@mikro-orm/migrations": "^5.2.2",
"@mikro-orm/nestjs": "^5.0.2",
"@mikro-orm/postgresql": "^5.2.0",
"@mikro-orm/postgresql": "^5.2.2",
"@nestjs/common": "^8.4.4",
"@nestjs/core": "^8.4.4",
"@nestjs/graphql": "^10.0.16",
@ -52,9 +51,9 @@
"mime-types": "^2.1.35",
"ms": "^3.0.0-canary.1",
"nanoid": "^3.3.4",
"nodemailer": "^6.7.5",
"nodemailer": "^6.7.6",
"normalize-url": "^6",
"passport": "^0.5.2",
"passport": "^0.6.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.13",
@ -64,22 +63,23 @@
"xbytes": "^1.7.0"
},
"devDependencies": {
"@mikro-orm/cli": "^5.2.0",
"@swc/core": "^1.2.206",
"@mikro-orm/cli": "^5.2.2",
"@swc/core": "^1.2.208",
"@sylo-digital/scripts": "^1.0.2",
"@types/bcrypt": "^5.0.0",
"@types/dedent": "^0.7.0",
"@types/jest": "^28.1.3",
"@types/jest": "^28.1.4",
"@types/luxon": "^2.3.2",
"@types/mime-types": "^2.1.1",
"@types/node": "16",
"@types/passport-jwt": "^3.0.6",
"@types/passport-local": "^1.0.34",
"@types/sharp": "^0.30.2",
"jest": "^28.1.0",
"@vercel/ncc": "^0.34.0",
"jest": "^28.1.2",
"pretty-bytes": "^6.0.0",
"ts-node": "^10.7.0",
"tsup": "^6.1.2",
"ts-node": "^10.8.2",
"tsup": "^6.1.3",
"typescript": "^4.7.4"
},
"mikro-orm": {

View File

@ -18,13 +18,14 @@ export const migrate = async (
return;
}
const migrations = await migrator.getPendingMigrations();
if (!migrations[0]) {
ormLogger.debug(`No pending migrations`);
const executedMigrations = await migrator.getExecutedMigrations();
const pendingMigrations = await migrator.getPendingMigrations();
if (!pendingMigrations[0]) {
ormLogger.debug(`No pending migrations, ${executedMigrations.length} already executed`);
return;
}
ormLogger.log(`Migrating through ${migrations.length} migrations`);
ormLogger.log(`Migrating through ${pendingMigrations.length} migrations`);
await em.transactional(async (em) => {
if (!skipLock) await em.execute(`LOCK TABLE ${migrationsTableName} IN EXCLUSIVE MODE`);
await migrator.up({ transaction: em.getTransactionContext() });

View File

@ -15,7 +15,7 @@ import { checkThumbnailSupport } from '@ryanke/thumbnail-generator';
import { Exclude } from 'class-transformer';
import mimeType from 'mime-types';
import { generateDeleteKey } from '../../helpers/generate-delete-key.helper';
import { ResourceBase } from '../../types';
import { Resource } from '../../helpers/resource.entity-base';
import { Paginated } from '../../types/paginated.type';
import { Thumbnail } from '../thumbnail/thumbnail.entity';
import { User } from '../user/user.entity';
@ -23,7 +23,7 @@ import { FileMetadata } from './file-metadata.embeddable';
@Entity({ tableName: 'files' })
@ObjectType()
export class File extends ResourceBase {
export class File extends Resource {
@PrimaryKey()
@Field(() => ID)
id: string;

View File

@ -2,12 +2,12 @@ import { Entity, IdentifiedReference, ManyToOne, OptionalProps, PrimaryKey, Prop
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { Exclude } from 'class-transformer';
import { generateContentId } from '../../helpers/generate-content-id.helper';
import { ResourceBase } from '../../types';
import { User } from '../user/user.entity';
import { Resource } from '../../helpers/resource.entity-base';
@Entity({ tableName: 'links' })
@ObjectType()
export class Link extends ResourceBase {
export class Link extends Resource {
@PrimaryKey()
@Field(() => ID)
id: string = generateContentId();

View File

@ -5,13 +5,13 @@ import { IsBoolean, IsNumber, IsOptional, IsString, Length } from 'class-validat
import mime from 'mime-types';
import { config } from '../../config';
import { generateContentId } from '../../helpers/generate-content-id.helper';
import { ResourceBase } from '../../types';
import { Resource } from '../../helpers/resource.entity-base';
import { Paginated } from '../../types/paginated.type';
import { User } from '../user/user.entity';
@Entity({ tableName: 'pastes' })
@ObjectType({ isAbstract: true })
export class Paste extends ResourceBase {
export class Paste extends Resource {
@PrimaryKey()
@Field(() => ID)
id: string = generateContentId();

View File

@ -6,12 +6,11 @@ import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/g
import ms from 'ms';
import { nanoid } from 'nanoid';
import { paginate, parseCursor } from '../../helpers/pagination';
import { File } from '../../types';
import { UserId } from '../auth/auth.decorators';
import { AuthService, TokenType } from '../auth/auth.service';
import { JWTAuthGuard } from '../auth/guards/jwt.guard';
import type { JWTPayloadUser } from '../auth/strategies/jwt.strategy';
import { FilePage } from '../file/file.entity';
import { File, FilePage } from '../file/file.entity';
import { InviteService } from '../invite/invite.service';
import { Paste, PastePage } from '../paste/paste.entity';
import { CreateUserDto } from './dto/create-user.dto';

View File

@ -1,14 +1,9 @@
import { EntityRepository, QueryOrder, UniqueConstraintViolationException } from '@mikro-orm/core';
import { InjectRepository } from '@mikro-orm/nestjs';
import {
BadRequestException,
ConflictException,
ForbiddenException,
Injectable
} from '@nestjs/common';
import { BadRequestException, ConflictException, ForbiddenException, Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import bcrypt from 'bcrypt';
import { readFileSync } from 'fs';
import dedent from 'dedent';
import { compile } from 'handlebars';
import ms from 'ms';
import { nanoid } from 'nanoid';
@ -25,12 +20,18 @@ import type { Pagination } from './dto/pagination.dto';
import { UserVerification } from './user-verification.entity';
import { User } from './user.entity';
const EMAIL_TEMPLATE_SOURCE = dedent`
<body>
<h1>Verify Your Email</h1>
<p>Thanks for signing up to micro. Click the link below to verify your email and activate your account.</p>
<a href="{{verifyUrl}}">{{verifyUrl}}</a>
<p><i>If you did not sign up for micro, ignore this email.</i></p>
`;
@Injectable()
export class UserService {
private static readonly VERIFICATION_EXPIRY = ms('6 hours');
private static readonly EMAIL_TEMPLATE = compile<{ verifyUrl: string }>(
readFileSync('templates/verify.handlebars', 'utf8')
);
private static readonly EMAIL_TEMPLATE = compile<{ verifyUrl: string }>(EMAIL_TEMPLATE_SOURCE);
constructor(
@InjectRepository(User) private readonly userRepo: EntityRepository<User>,
@ -42,7 +43,7 @@ export class UserService {
async getUser(id: string, verified: boolean) {
const user = await this.userRepo.findOneOrFail(id);
if ( verified && config.email && !user.verifiedEmail) {
if (verified && config.email && !user.verifiedEmail) {
throw new ForbiddenException('You must verify your email first.');
}

View File

@ -1,35 +0,0 @@
import type { AppController } from './modules/app.controller';
import type { File } from './modules/file/file.entity';
import type { InviteController } from './modules/invite/invite.controller';
import type { CreatePasteDto, Paste } from './modules/paste/paste.entity';
import type { UserController } from './modules/user/user.controller';
export type Await<T> = T extends {
then: (onfulfilled?: (value: infer U) => unknown) => unknown;
}
? U
: T;
// invite
export type GetInviteData = Await<ReturnType<InviteController['getInvite']>>;
// user
export type GetUserData = Await<ReturnType<UserController['getUser']>>;
export type GetUserFilesData = Await<ReturnType<UserController['getUserFiles']>>;
export type GetUserPastesData = Await<ReturnType<UserController['getUserPastes']>>;
export type GetUploadTokenData = Await<ReturnType<UserController['getUserToken']>>;
export type PutUploadTokenData = Await<ReturnType<UserController['resetUserToken']>>;
// file
export type GetFileData = File;
// app
export type GetServerConfigData = Await<ReturnType<AppController['getConfig']>>;
export type GetPasteData = Paste;
export type CreatePasteBody = CreatePasteDto;
export { Resource as ResourceBase } from './helpers/resource.entity-base';
export type { ResourcePaths } from './helpers/resource.entity-base';
export { File } from './modules/file/file.entity';
export { User } from './modules/user/user.entity';

View File

@ -1,5 +0,0 @@
<body>
<h1>Verify Your Email</h1>
<p>Thanks for signing up to micro. Click the link below to verify your email and activate your account.</p>
<a href="{{verifyUrl}}">{{verifyUrl}}</a>
<p><i>If you did not sign up for micro, ignore this email.</i></p>

View File

@ -21,13 +21,13 @@
},
"devDependencies": {
"@types/fluent-ffmpeg": "^2.1.20",
"@types/jest": "^28.1.3",
"@types/jest": "^28.1.4",
"@types/mime-types": "^2.1.1",
"@types/node": "^16.11.21",
"@types/sharp": "^0.30.2",
"eslint": "^8.16.0",
"jest": "^28.1.0",
"tsup": "^6.1.2",
"eslint": "^8.19.0",
"jest": "^28.1.2",
"tsup": "^6.1.3",
"typescript": "^4.7.4"
},
"jest": {

View File

@ -1,4 +1,10 @@
const path = require('path');
module.exports = {
output: 'standalone',
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'),
},
async rewrites() {
return [
{

View File

@ -10,7 +10,6 @@
},
"scripts": {
"watch": "NODE_ENV=development concurrently \"next dev\" \"pnpm generate --watch\"",
"start": "NODE_ENV=production next start",
"build": "NODE_ENV=production next build",
"lint": "NODE_ENV=production next lint",
"generate": "graphql-codegen --config codegen.yml"
@ -28,9 +27,9 @@
"graphql": "^16.5.0",
"http-status-codes": "^2.2.0",
"nanoid": "^3.3.4",
"next": "12.1.6",
"next": "12.2.0",
"postcss": "^8.4.13",
"prism-react-renderer": "^1.3.1",
"prism-react-renderer": "^1.3.5",
"react": "18.2.0",
"react-dom": "^18.1.0",
"react-feather": "^2.0.9",
@ -39,20 +38,20 @@
"remark-gfm": "^3.0.1",
"swr": "^1.3.0",
"tailwindcss": "^3.0.24",
"deepmerge": "^4.2.2",
"concurrently": "^7.2.2",
"lodash": "^4.17.21",
"yup": "^0.32.11"
},
"devDependencies": {
"@graphql-codegen/cli": "^2.6.2",
"@graphql-codegen/typescript": "2.5.1",
"@graphql-codegen/typescript-operations": "2.4.2",
"@graphql-codegen/typescript-react-apollo": "3.2.16",
"@graphql-codegen/cli": "^2.7.0",
"@graphql-codegen/typescript": "2.6.0",
"@graphql-codegen/typescript-operations": "2.4.3",
"@graphql-codegen/typescript-react-apollo": "3.2.17",
"@sylo-digital/scripts": "^1.0.2",
"@types/lodash": "^4.14.182",
"@types/node": "16",
"@types/react": "^18.0.8",
"concurrently": "^7.2.2",
"deepmerge": "^4.2.2",
"lodash": "^4.17.21",
"typescript": "^4.7.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
],
"outputs": [
"dist/**",
".next/**"
".next/{server,cache,src,static}"
]
},
"lint": {

View File

@ -1,7 +1,7 @@
#!/bin/bash
#!/bin/sh
cd packages/api && npm run start &
cd packages/web && npm run start &
cd packages/api && node ./dist/index.js &
cd packages/web && node ./server.js &
wait -n
exit $?