Add Docker Build Support
Blog posts with 'draft: true' in the frontmatter are excluded from the production artifact --no-cache docker builds ensure fresh database build each time. Caching isn't needed do to small size anyway
This commit is contained in:
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
.env
|
||||||
|
.editorconfig
|
||||||
|
coverage*
|
||||||
|
dist
|
||||||
|
*.log
|
||||||
|
blog.sqlite
|
||||||
|
Dockerfile*
|
||||||
|
docker-compose*
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
blog.sqlite
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
@@ -25,6 +26,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
|
|||||||
51
Dockerfile
Normal file
51
Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM oven/bun:1 AS builder
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY package.json bun.lock ./
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy source code and content
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Copy database initialization script
|
||||||
|
COPY init-db.ts ./
|
||||||
|
|
||||||
|
# Initialize database using init-db.ts script
|
||||||
|
# This script handles draft-aware post verification and stability detection
|
||||||
|
ENV NODE_ENV=build
|
||||||
|
RUN bun run init-db.ts
|
||||||
|
|
||||||
|
# Final production stage
|
||||||
|
FROM oven/bun:1
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy dependencies
|
||||||
|
COPY --from=builder /usr/src/app/node_modules ./node_modules
|
||||||
|
COPY --from=builder /usr/src/app/package.json ./
|
||||||
|
|
||||||
|
# Copy source code (for runtime transpilation)
|
||||||
|
COPY --from=builder /usr/src/app/src ./src
|
||||||
|
COPY --from=builder /usr/src/app/content ./content
|
||||||
|
COPY --from=builder /usr/src/app/bun_plugins ./bun_plugins
|
||||||
|
COPY --from=builder /usr/src/app/index.tsx .
|
||||||
|
COPY --from=builder /usr/src/app/tsconfig.json .
|
||||||
|
|
||||||
|
# Copy the initialized database for efficient layer caching
|
||||||
|
COPY --from=builder /usr/src/app/blog.sqlite ./
|
||||||
|
|
||||||
|
# Fix ownership for bun user (needed for runtime operations)
|
||||||
|
RUN chown -R bun:bun /usr/src/app
|
||||||
|
|
||||||
|
# Switch to bun user
|
||||||
|
USER bun
|
||||||
|
|
||||||
|
# Expose the default port
|
||||||
|
EXPOSE 3000/tcp
|
||||||
|
|
||||||
|
# Production environment (onStartup-post-importer won't run since DB exists)
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Run the application directly (Bun handles TSX transpilation at runtime)
|
||||||
|
ENTRYPOINT [ "bun", "run", "index.tsx" ]
|
||||||
@@ -14,6 +14,11 @@ import { dbConnection } from "../src/db";
|
|||||||
const {data, content } = matter(await Bun.file(`./content/${file}`).text());
|
const {data, content } = matter(await Bun.file(`./content/${file}`).text());
|
||||||
const route = `/${file.replace(/\.md$/, "")}`;
|
const route = `/${file.replace(/\.md$/, "")}`;
|
||||||
|
|
||||||
|
// Omit draft blog posts from database initialization in production
|
||||||
|
if (process.env.NODE_ENV === 'build' && data.draft) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the title from content if it matches the frontmatter title to avoid duplicate H1s
|
// Remove the title from content if it matches the frontmatter title to avoid duplicate H1s
|
||||||
let processedContent = content;
|
let processedContent = content;
|
||||||
if (data.title) {
|
if (data.title) {
|
||||||
|
|||||||
85
init-db.ts
Normal file
85
init-db.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
const { Database } = require("bun:sqlite");
|
||||||
|
|
||||||
|
async function waitForDatabase() {
|
||||||
|
const serverProcess = Bun.spawn(["bun", "run", "index.tsx"], {
|
||||||
|
env: { ...process.env, NODE_ENV: "development" },
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Count expected markdown files - this gives us total potential posts
|
||||||
|
const files = [];
|
||||||
|
for await (const file of new Bun.Glob("**/*.md").scan("./content")) {
|
||||||
|
files.push(file);
|
||||||
|
}
|
||||||
|
const totalMarkdownFiles = files.length;
|
||||||
|
console.log(`Found ${totalMarkdownFiles} markdown files to process`);
|
||||||
|
|
||||||
|
const maxWait = 30000; // 30 seconds total timeout
|
||||||
|
const checkInterval = 100; // Check every 100ms
|
||||||
|
let waited = 0;
|
||||||
|
let stableCount = 0;
|
||||||
|
let previousCount = -1;
|
||||||
|
|
||||||
|
while (waited < maxWait) {
|
||||||
|
if (await Bun.file("blog.sqlite").exists()) {
|
||||||
|
try {
|
||||||
|
const db = new Database("blog.sqlite", { readonly: true });
|
||||||
|
|
||||||
|
// Count only published posts (routes ending without .md)
|
||||||
|
// This matches the logic used in the actual application queries
|
||||||
|
const result = db.query("SELECT COUNT(*) as count FROM posts WHERE path NOT LIKE '%.md'").get();
|
||||||
|
const publishedPosts = result.count;
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
if (publishedPosts === previousCount) {
|
||||||
|
stableCount++;
|
||||||
|
} else {
|
||||||
|
stableCount = 0;
|
||||||
|
previousCount = publishedPosts;
|
||||||
|
console.log(`Importing progress: ${publishedPosts}/${totalMarkdownFiles} published posts`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the count has been stable for 10 consecutive checks (1 second), consider import complete
|
||||||
|
// We check against available posts, not total markdown files, since some may be drafts
|
||||||
|
if (stableCount >= 10 && publishedPosts >= 1) {
|
||||||
|
console.log(`Import complete: ${publishedPosts} published posts ready`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Database might still be writing or locked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
||||||
|
waited += checkInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we succeeded
|
||||||
|
if (!(await Bun.file("blog.sqlite").exists())) {
|
||||||
|
console.error("Database file was not created");
|
||||||
|
await serverProcess.kill();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final verification - check published posts count
|
||||||
|
const db = new Database("blog.sqlite", { readonly: true });
|
||||||
|
const result = db.query("SELECT COUNT(*) as count FROM posts WHERE path NOT LIKE '%.md'").get();
|
||||||
|
const finalPublishedPosts = result.count;
|
||||||
|
|
||||||
|
// Also get total posts for informational purposes
|
||||||
|
const totalResult = db.query("SELECT COUNT(*) as count FROM posts").get();
|
||||||
|
const totalPosts = totalResult.count;
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
if (finalPublishedPosts < 1) {
|
||||||
|
console.error(`No published posts found! Total: ${totalPosts}, Published: ${finalPublishedPosts}`);
|
||||||
|
await serverProcess.kill();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await serverProcess.kill();
|
||||||
|
console.log(`Database initialization complete: ${totalPosts} total posts (${finalPublishedPosts} published)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForDatabase();
|
||||||
@@ -2,8 +2,11 @@
|
|||||||
"name": "portfolio",
|
"name": "portfolio",
|
||||||
"version": "1.0.50",
|
"version": "1.0.50",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"dev": "bun run --watch ./index.tsx",
|
||||||
"dev": "bun run --watch ./index.tsx"
|
"build": "bun run docker:build",
|
||||||
|
"docker:build": "docker build --no-cache -t blog:latest .",
|
||||||
|
"docker:run": "docker run -d -p 3000:3000 --name blog blog:latest",
|
||||||
|
"docker:stop": "docker stop blog && docker rm blog"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user