{"id":21719874,"url":"https://github.com/moritzrinow/coreblog","last_synced_at":"2026-02-11T03:31:33.649Z","repository":{"id":264662783,"uuid":"880376730","full_name":"moritzrinow/coreblog","owner":"moritzrinow","description":"Super simple blog engine using S3. No database required. ","archived":false,"fork":false,"pushed_at":"2024-12-05T22:59:30.000Z","size":903,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-12T20:52:11.902Z","etag":null,"topics":["blazor","blog","blog-engine","blogs","cms","s3"],"latest_commit_sha":null,"homepage":"https://coreblog-demo.moritzrinow.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moritzrinow.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGE.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-29T16:01:53.000Z","updated_at":"2025-03-16T15:38:54.000Z","dependencies_parsed_at":"2024-11-25T16:43:28.678Z","dependency_job_id":"c4c31d8c-a5b3-4168-bb16-8d38138e48ba","html_url":"https://github.com/moritzrinow/coreblog","commit_stats":null,"previous_names":["moritzrinow/coreblog"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/moritzrinow/coreblog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moritzrinow%2Fcoreblog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moritzrinow%2Fcoreblog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moritzrinow%2Fcoreblog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moritzrinow%2Fcoreblog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moritzrinow","download_url":"https://codeload.github.com/moritzrinow/coreblog/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moritzrinow%2Fcoreblog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29326154,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-11T02:08:56.257Z","status":"ssl_error","status_checked_at":"2026-02-11T02:08:51.338Z","response_time":97,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blazor","blog","blog-engine","blogs","cms","s3"],"created_at":"2024-11-26T01:42:48.772Z","updated_at":"2026-02-11T03:31:33.624Z","avatar_url":"https://github.com/moritzrinow.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CoreBlog\n\nCoreBlog is a super simple blog engine, that does not require a database.\nAll you need is an S3 bucket.\n\nDemo: https://coreblog-demo.moritzrinow.com\n\n\u003e [!WARNING]\n\u003e This project is still in the early alpha phase. Breaking changes might be introduced any time until version 1.0.0.\n\n- ✅ Multiple themes including dark and white modes\n- ✅ Supports multiple languages including `en`, `de`, `es`, `it`, `fr`, `uk`, `ru`\n- ✅ SEO friendly\n- ✅ Low resource requirements (100MiB RAM)\n- ✅ No database required\n- ✅ Multiple fonts available\n- ✅ Allows embedding assets in posts\n- ✅ Docker image\n- ✅ Mobile friendly\n- ✅ RSS Feed\n- ❌ Sadly no custom UI to edit the blog yet (planned for the future)\n  - See [Manage your blog with Terraform](#manage-your-blog-with-terraform)\n- ❌ No real-time blog/post updates (data is pulled in intervals)\n\n---\n\n- [Configuration](#configuration)\n- [Install with Docker](#install-with-docker)\n- [S3 bucket layout and file formats](#s3-bucket-layout-and-file-formats)\n  - [Blog manifest (`blog.json`)](#blog-manifest-blogjson)\n  - [Post manifest (`post-id.json`)](#post-manifest-post-idjson)\n  - [Embedding images (assets)](#embedding-images-assets)\n- [Manage your blog with Terraform](#manage-your-blog-with-terraform)\n\n---\n\n## Configuration\n\n`coreblog.yaml`:\n\n```yaml\ns3:\n  endpoint: 'https://fsn1.your-objectstorage.com'\n  region: 'fsn1'\n  bucketName: 'my-blog' # Required, should be private\n  assetBucketName: 'my-blog-assets' # Optional, must be publicly accessible\n  accessKey: 'HDY...'\n  secretKey: 'r78...'\nsyncPeriodMinutes: 60 # Every hour blog and post metadata is pulled from S3\npostContentCacheTtlMinutes: 60 # Post content is cached in-memory\n```\n\n---\n\n## Install with Docker\n\nCreate a `coreblog.yaml` file with your configurations.\n\nRun the docker container:\n\n```text\ndocker run -p 5000:5000 -v /my/coreblog.yaml:/coreblog/coreblog.yaml devmojo/coreblog\n```\n\nYou can specify a different path for the config file:\n\n```text\ndocker run devmojo/coreblog --config /path/to/config.yaml\n```\n\nOr specify everything with environment variables:\n\n- CB_S3__Endpoint\n- CB_S3__Region\n- CB_S3__BucketName\n- CB_S3__AssetBucketName\n- CB_S3__AccessKey\n- CB_S3__SecretKey\n- CB_SyncPeriodMinutes\n- CB_PostContentCacheTtlMinutes\n\n---\n\n## S3 bucket layout and file formats\n\n```text\nblog.json\nposts/\n    post-1.json\n    post-1.md\n    post-2.json\n    post-2.md\n```\n\nThe S3 bucket stores 3 different kind of files:\n\n- 1 blog manifest file (`blog.json`)\n  - Contains metadata about the blog\n- N post manifest files (`posts/post-id.json`)\n  - Contains metadata about a post\n- N post content files (`posts/post-id.md`)\n  - Contains the post content written in Markdown\n\n### Blog manifest (`blog.json`):\n\n```json5\n{\n  \"title\": \"CoreBlog Demo\",\n  \"author\": \"CoreBlog\",\n  \"description\": \"Welcome to the CoreBlog demo!\",\n  \"homepage\": \"https://github.com/moritzrinow/coreblog\",\n  // Your personal homepage\n  \"theme\": \"standard\",\n  \"language\": \"en\",\n  // Controls language of UI elements\n  \"fontFamily\": \"Roboto Mono\",\n  \"lineHeight\": 2.0,\n  \"additionalPageMeta\": {\n    // You can use this to perform things like Google site verifications\n  }\n}\n```\n\nIf the property `homepage` is specified, a link named 'About Me' will be displayed in the header.\n\nSupported themes:\n\n- `material`\n- `material-dark`\n- `standard`\n- `standard-dark` (used in demo)\n- `default`\n- `dark`\n- `humanistic`\n- `humanistic-dark`\n- `software`\n- `software-dark`\n\nSupported fonts:\n\n- `Roboto Mono`\n- `Roboto`\n- `sans-serif`\n\nSupported languages:\n\n- `en` (English)\n- `de` (German)\n- `es` (Spanish)\n- `it` (Italian)\n- `fr` (French)\n- `uk` (Ukrainian)\n- `ru` (Russian)\n\nIf you want to display a **favicon**, you have to put a file named `favicon.ico` in the asset bucket.\n\n### Post manifest (`post-id.json`):\n\n```json5\n{\n  \"id\": \"example-1\",\n  // URL slug, must be same as file prefix\n  \"title\": \"How to host your own managed Kubernetes cluster in the cloud\",\n  \"language\": \"en\",\n  // Language of the actual content\n  \"summary\": \"In this post I will show you how to host your own Kubernetes cluster in the cloud.\",\n  \"published\": true,\n  \"hidden\": false,\n  // Can be used to have unlisted posts\n  \"date\": \"2024-11-21\",\n  \"thumbnail\": \"welcome.jpg\",\n  // Image stored in asset bucket\n  \"tags\": [\n    \"kubernetes\",\n    \"cloud\"\n  ]\n}\n```\n\n### Embedding images (assets)\n\nIf you want to make use of static assets, such as images, an optional second S3 bucket comes into play.\n\nIt **must** be publicly accessible to serve the assets directly.\n\nTo include assets in your blog-post Markdown, use relative links prefixed with `assets/`:\n\n```markdown\nThe following is an image from my assets bucket:\n![My Image](assets/my-image.png)\n```\n\nLinks starting with `assets/` will be rewritten to point to the S3 asset-bucket directly.\n\n---\n\n## Manage your blog with Terraform\n\nSince your whole blog consists of simple files stored in S3, we can make use of the\n[AWS Terraform Provider](https://registry.terraform.io/providers/hashicorp/aws/latest) to streamline the management\nof our blog.\n\nYou can use your IDE of choice to edit your Markdown/JSON files and let Terraform sync that with S3.\nSame goes for the assets.\n\nYour Terraform module structure:\n\n```text\nmain.tf\nblog.tf\nblog-posts/\n    post-1.json\n    post-1.md\n    post-2.json\n    post-2.md\nblog-assets/\n    favicon.ico\n```\n\nExample provider configuration for [S3 on Hetzner](https://docs.hetzner.com/storage/object-storage/overview):\n\n```terraform\n// Provider requirements...\n\nprovider \"aws\" {\n  region                      = local.s3_region\n  access_key                  = var.s3_access_key\n  secret_key                  = var.s3_secret_key\n  skip_region_validation      = true\n  skip_requesting_account_id  = true\n  skip_metadata_api_check     = true\n  skip_credentials_validation = true\n  endpoints {\n    s3 = \"https://fsn1.your-objectstorage.com\"\n  }\n}\n```\n\n`blog.tf`:\n\n```terraform\n// Main bucket\nresource \"aws_s3_bucket\" \"blog\" {\n  bucket = \"my-blog\"\n}\n\n// Asset bucket\nresource \"aws_s3_bucket\" \"blog_assets\" {\n  bucket = \"my-blog-assets\"\n}\n\n// ACL to allow public-read access to asset bucket\nresource \"aws_s3_bucket_acl\" \"blog_assets\" {\n  bucket = aws_s3_bucket.blog.id\n  acl    = \"public-read\"\n}\n\n// Our blog manifest (blog.json)\nresource \"aws_s3_object\" \"blog_manifest\" {\n  bucket = aws_s3_bucket.blog.bucket\n  key    = \"blog.json\"\n  content = jsonencode({\n    title              = \"CoreBlog Demo\"\n    author             = \"CoreBlog\"\n    description        = \"Welcome to the CoreBlog demo!\"\n    homepage           = \"https://github.com/moritzrinow/coreblog\"\n    theme              = \"standard-dark\"\n    language           = \"en\"\n    fontFamily         = \"Roboto Mono\"\n    additionalPageMeta = {}\n  })\n}\n\n// Post manifest files\nresource \"aws_s3_object\" \"blog_posts_manifest\" {\n  for_each = fileset(\"${path.module}/blog-posts\", \"*.json\")\n  bucket   = aws_s3_bucket.blog.bucket\n  key      = \"posts/${each.value}\"\n  source   = \"${path.module}/blog-posts/${each.value}\"\n  etag     = filemd5(\"${path.module}/blog-posts/${each.value}\")\n}\n\n// Post content files\nresource \"aws_s3_object\" \"blog_posts_content\" {\n  for_each = fileset(\"${path.module}/blog-posts\", \"*.md\")\n  bucket   = aws_s3_bucket.blog.bucket\n  key      = \"posts/${each.value}\"\n  source   = \"${path.module}/blog-posts/${each.value}\"\n  etag     = filemd5(\"${path.module}/blog-posts/${each.value}\")\n}\n\n// Blog assets\nresource \"aws_s3_object\" \"blog_assets\" {\n  for_each = fileset(\"${path.module}/blog-assets\", \"*\")\n  bucket   = aws_s3_bucket.blog_assets.bucket\n  key      = each.value\n  source   = \"${path.module}/blog-assets/${each.value}\"\n  etag     = filemd5(\"${path.module}/blog-assets/${each.value}\")\n}\n\n```\n\nIf you modify or add files in the specified directories, Terraform will ensure the specific changes are synced with S3.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoritzrinow%2Fcoreblog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoritzrinow%2Fcoreblog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoritzrinow%2Fcoreblog/lists"}