Giter Club home page Giter Club logo

ghost-on-kubernetes's Introduction

Ghost on Kubernetes by SREDevOps.Org

SREDevOps.org

Community for SRE, DevOps, Cloud Native, GNU/Linux, and more. ๐ŸŒŽ

Build Multiarch | Image Size | OpenSSF Scorecard | Fork this repository | Star this repository | OpenSSF Best Practices

This repository implements Ghost CMS v5.xx.x from @TryGhost (upstream) on Kubernetes, with our custom image, which has significant improvements to be used on Kubernetes. See this whole README for more information.

Star History

Star History Chart

Recent Changes

We've made some significant updates to improve the security and efficiency of our Ghost implementation on Kubernetes:

  1. Multi-arch support: The images are now multi-arch, with support for amd64 and arm64.
  2. Distroless Image: We use @GoogleContainerTools's Distroless NodeJS as the execution environment for the final image. Distroless images are minimal images that contain only the necessary components to run the application, making them more secure and efficient than traditional images.
  3. MySQL StatefulSet: We've changed the MySQL implementation to a StatefulSet. This provides stable network identifiers and persistent storage, which is important for databases like MySQL that need to maintain state.
  4. Init Container: We've added an init container to the Ghost deployment. This container is responsible for setting up the necessary configuration files and directories before the main Ghost container starts, ensuring the right directories are created, correct ownership for user node inside distroless container UID/GID to 65532, and the correct permissions are set. Check deploy/06-ghost-deployment.yaml for details on these changes.
  5. Entrypoint Script: We've introduced a new entrypoint script that runs as the non-privileged user inside the distroless container. This script is responsible for updating the default themes then starts the Ghost application. This script is executed by the nonroot user without privileges within the Distroless container, which updates default themes and starts the Ghost application, operation performed into the distroless container in runtime. entrypoint.js
#
# This Dockerfile is used to build a container image for running Ghost, a popular open-source blogging platform, on Kubernetes.
# The image is built with the official Node 20 on Debian Bookworm (LTS Iron) image and uses the Distroless base image for security and minimalism.
#
# Stage 1: Build Environment
# In this stage, the build environment is set up and the necessary dependencies are installed.
# The Ghost version is defined as a build argument and set as an environment variable.
# The installation directory, content directory, and original content directory for Ghost are also set as environment variables.
# The Ghost CLI is installed globally and configured with some workarounds to build the arm64 version in GitHub without timeout failures.
# Ghost is then installed with the specified version, using MySQL as the database, and configured without prompts, stack traces, and setup.
# The original content directory is moved to a backup location, a new content directory is created, and the correct ownership and permissions are set.
#
# Stage 2: Final Image
# In this stage, the final image is created using the Distroless base image.
# The Ghost installation directory is copied from the build environment to the final image.
# The working directory is set to the Ghost installation directory and a volume is created for the content directory.
# The entrypoint script is copied to the current Ghost version.
# Port 2368 is exposed for Ghost.
# The command is set to start Ghost with the entrypoint script.
#
# For more information, refer to the GitHub repository: https://github.com/sredevopsorg/ghost-on-kubernetes

# Stage 1: Build Environment
FROM node:iron-bookworm@sha256:786005cf39792f7046bcd66491056c26d2dbcc669c072d1a1e4ef4fcdddd26eb AS build-env
...

# Stage 2: Final Image
FROM gcr.io/distroless/nodejs20-debian12
...
# This Dockerfile is used to build a container image for running Ghost, a popular open-source blogging platform, on Kubernetes.
# The image is built with official Node 20 on Debian Bookworm (LTS Iron)  image and uses the Distroless base image for security and minimalism.

# Stage 1: Build Environment
FROM node:iron-bookworm@sha256:786005cf39792f7046bcd66491056c26d2dbcc669c072d1a1e4ef4fcdddd26eb AS build-env
USER root
# Create a new user and group named "nonroot" with the UID 65532 and GID 65532, not a member of the root, sudo, and sys groups, and set the home directory to /home/nonroot.
# This user is used to run the Ghost application in the container for security reasons.
RUN groupadd -g 65532 nonroot && \
    useradd -u 65532 -g 65532 -d /home/nonroot nonroot && \
    usermod -aG nonroot nonroot && \
    mkdir -pv /home/nonroot && \
    chown -Rfv 65532:65532 /home/nonroot

USER nonroot
SHELL ["/bin/bash", "-c"]
ENV NODE_ENV=production NPM_CONFIG_LOGLEVEL=info

# Define the GHOST_VERSION build argument and set it as an environment variable
ARG GHOST_VERSION 
ENV GHOST_VERSION=$GHOST_VERSION  

# Set the installation directory, content directory, and original content directory for Ghost
ENV GHOST_INSTALL=/home/nonroot/app/ghost
ENV GHOST_CONTENT=/home/nonroot/app/ghost/content
ENV GHOST_CONTENT_ORIGINAL=/home/nonroot/app/ghost/content.orig

RUN mkdir -pv "$GHOST_INSTALL"
    
# Install the latest version of Ghost CLI globally and config some workarounds to build arm64 version in Github without timeout failures
RUN yarn config set network-timeout 60000 && \
    yarn config set inline-builds true && \
    npm config set fetch-timeout 60000 && \
    npm config set progress && \
    npm config set omit dev

# Create the Ghost installation directory and set the owner to the "node" user


# RUN npm i -g ghost-cli@latest || yarn global add ghost-cli@latest

# Install Ghost with the specified version, using MySQL as the database, and configure it without prompts, stack traces, setup, and in the specified installation directory
# RUN ghost install $GHOST_VERSION --dir $GHOST_INSTALL --db mysql --dbhost mysql --no-prompt --no-stack --no-setup --color --process local || 

RUN npx ghost-cli install $GHOST_VERSION --dir $GHOST_INSTALL --db mysql --dbhost mysql --no-prompt --no-stack --no-setup --color --process local


# Move the original content directory to a backup location, create a new content directory, set the correct ownership and permissions, and switch back to the "node" user
RUN mv -v $GHOST_CONTENT $GHOST_CONTENT_ORIGINAL && \
    mkdir -v $GHOST_CONTENT && \
    # chown -Rfv 65532 $GHOST_CONTENT_ORIGINAL && \
    # chown -Rfv 65532 $GHOST_CONTENT && \
    # chown -fv 65532 $GHOST_INSTALL && \
    chmod -v 0775 $GHOST_CONTENT

# Switch back to the "node" user
# USER node

# Stage 2: Final Image
FROM gcr.io/distroless/nodejs20-debian12

# Set the installation directory and content directory for Ghost
ENV GHOST_INSTALL_SRC=/home/nonroot/app/ghost
ENV GHOST_INSTALL=/home/nonroot/app/ghost
ENV GHOST_CONTENT=/home/nonroot/app/ghost/content
ENV GHOST_CONTENT_ORIGINAL=/home/nonroot/app/ghost/content.orig
USER nonroot

# Copy the Ghost installation directory from the build environment to the final image
COPY --from=build-env $GHOST_INSTALL_SRC $GHOST_INSTALL

# Set the working directory to the Ghost installation directory and create a volume for the content directory
# The volume is used to persist the data across container restarts, upgrades, and migrations. 
# It's going to be handled with an init container that will copy the content from your original content directory to the new content directory (If there is any)
# The CMD script will handle default themes included (Casper and Source) and init Ghost.

WORKDIR $GHOST_INSTALL
VOLUME $GHOST_CONTENT

# Copy the entrypoint script to the current Ghost version.
COPY --chown=65532 entrypoint.js current/entrypoint.js


# Expose port 2368 for Ghost
EXPOSE 2368

# Set the command to start Ghost with the entrypoint (See https://github.com/sredevopsorg/ghost-on-kubernetes/blob/main/entrypoint.js)
CMD ["current/entrypoint.js"]

Features

  • ARM64 Support!
  • We use the official Node 20 Iron Bookworm image as our build environment. Dockerfile
  • We introduce a multi-stage build, which reduces the final image size, and improves security by removing unnecessary components from the final image.
  • Distroless Node 20 Debian 12 as our runtime environment for the final stage of the image.
  • Removed gosu, now everything runs as the default Node user (UID 1000:GID 1000) inside the Distroless container. This change itself reduces 2 critical vulnerabilities and 33 high vulnerabilities reported by Docker Scout in the original Ghost image (References: Ghost Official Image and Ghost on Kubernetes Image on Docker Hub at the time of writing).
  • New Entrypoint flow, using a Node.js script executed by the Node user without privileges within the Distroless container, which updates default themes and starts the Ghost application, operation which is performed into the distroless container itself.
  • We use the latest version of Ghost 5 (when the image is built).

ARM64 Compatible

Installation

0. Clone the repository or fork it

# Clone the repository
git clone https://github.com/sredevopsorg/ghost-on-kubernetes.git --depth 1 --branch main --single-branch --no-tags
# Change directory
cd ghost-on-kubernetes
# Create a new branch for your changes (optional but recommended).
git checkout -b my-branch --no-track --detach

1. Check the example configurations

  • There are some example configuration files in the examples directory. We use the stored configuration as a kind: Secret in the ghost-on-kubernetes namespace for Ghost and MySQL configuration. There are two example configuration files:
    • config.development.sample.yaml: This configuration file is for the Ghost development environment. It uses SQLite as the database. It can be useful if you want to test the Ghost configuration before implementing it in a production environment.
    • config.production.sample.yaml: This configuration file is for the Ghost production environment. It uses MySQL 8, and is the recommended configuration for production environments. It requires a valid top-level domain (TLD) and configuration for Ingress to access Ghost from the Internet.
  • If you need more information on the configuration, check the official Ghost documentation.

2. Review the default values and make changes as needed in the following files

  • deploy/00-namespace.yaml # Change the namespace according to your needs
  • deploy/01-secrets.yaml
# deploy/01-secrets.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mysql-ghost-on-kubernetes
  namespace: ghost-on-kubernetes
type: Opaque
stringData:
  MYSQL_DATABASE: mysql-db-name # Same as in deploy/04-config.production.yaml
  MYSQL_USER: mysql-db-user # Same as in deploy/04-config.production.yaml
  MYSQL_PASSWORD: mysql-db-password # Same as in deploy/04-config.production.yaml
  MYSQL_ROOT_PASSWORD: mysql-db-root-password # Same as in deploy/04-config.production.yaml
  • deploy/02-pvc.yaml # Change the storageClassName according to your needs
  • deploy/03-services.yaml # Change the hosts according to your needs
  • deploy/04-config.production.yaml # Change the values according to the secrets and services
  • deploy/05-mysql.yaml # Change the values according to the secrets and services
  • deploy/06-ghost-deployment.yaml # Change the values according to the secrets and services
  • deploy/07-ingress.yaml # Optional

3. Apply your manifests

# Before applying the manifests, make sure you are in the root directory of the repository
# ๐Ÿšจ Be sure to not change the filenames, also be sure to modify the files according to your needs before applying them.
# Why? Just because we need to deploy them in order. If you change the filenames, you will need to apply them one by one in the correct order.
kubectl apply -f ./deploy

4. Access your Ghost CMS

# Get the ingress IP, if you have configured the Ingress
kubectl get ingress -n ghost-on-kubernetes -o wide 

# Alternatively, create a port-forwarding rule to access the Ghost CMS
kubectl port-forward -n ghost-on-kubernetes service/service-ghost-on-kubernetes 2368:2368

5. Open your browser and access your Ghost CMS

6. Log in to your Ghost CMS Admin Panel

Contributing

We welcome contributions from the community! Please check the CONTRIBUTING.md file for more information on how to contribute to this project.

License and Credits

  • This project is licensed under the GNU General Public License v3.0. Please check the LICENSE file for more information.
  • The Ghost CMS is licensed under the MIT License.
  • The node:20 image and the Distroless image are licensed by their respective owners.

ghost-on-kubernetes's People

Contributors

dependabot[bot] avatar ngeorger avatar renovate[bot] avatar snyk-bot avatar sredevopsbot avatar step-security-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

ghost-on-kubernetes's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

dockerfile
Dockerfile
  • node iron-bookworm@sha256:a4d1de4c7339eabcf78a90137dfd551b798829e3ef3e399e0036ac454afa1291
  • gcr.io/distroless/nodejs20-debian12 latest@sha256:40b1db5c38e83ef8245efabcd9b5cbd87e2405eb75afe7fdea60ffb29c1a3289
github-actions
.github/workflows/build-custom-image.yaml
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/checkout v4.1.7@692973e3d937129bcbf40652eb9f2f61becf3332
  • docker/setup-buildx-action v3.6.1@988b5a0280414f521da01fcc63a27aeeb4b104db
  • docker/metadata-action v5.5.1@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
  • docker/login-action v3.3.0@9780b0c442fbb1117ed29e0efdff1e18412f7567
  • docker/login-action v3.3.0@9780b0c442fbb1117ed29e0efdff1e18412f7567
  • docker/login-action v3.3.0@9780b0c442fbb1117ed29e0efdff1e18412f7567
  • docker/build-push-action v6.7.0@5cd11c3a4ced054e52742c5fd54dca954e0edd85
.github/workflows/build-pr-tests.yaml
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/checkout v4.1.7@692973e3d937129bcbf40652eb9f2f61becf3332
  • docker/setup-buildx-action v3.6.1@988b5a0280414f521da01fcc63a27aeeb4b104db
  • docker/build-push-action v6.7.0@5cd11c3a4ced054e52742c5fd54dca954e0edd85
.github/workflows/codeql.yml
  • actions/checkout v4
  • github/codeql-action v3
  • github/codeql-action v3
.github/workflows/dependency-review.yml
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/checkout v4.1.7@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions/dependency-review-action v4.3.4@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c
.github/workflows/jekyll-gh-pages.yml
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/checkout v4.1.7@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions/configure-pages v5.0.0@983d7736d9b0ae728b81ab479565c72886d7745b
  • actions/jekyll-build-pages v1.0.13@44a6e6beabd48582f863aeeb6cb2151cc1716697
  • actions/upload-pages-artifact v3.0.1@56afc609e74202658d3ffba0e8f6dda462b719fa
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/deploy-pages v4.0.5@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e
.github/workflows/multi-build.yaml
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/checkout v4.1.7@692973e3d937129bcbf40652eb9f2f61becf3332
  • docker/metadata-action v5.5.1@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
  • docker/setup-qemu-action v3.2.0@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf
  • docker/setup-buildx-action v3.6.1@988b5a0280414f521da01fcc63a27aeeb4b104db
  • docker/login-action v3.3.0@9780b0c442fbb1117ed29e0efdff1e18412f7567
  • docker/build-push-action v6.7.0@5cd11c3a4ced054e52742c5fd54dca954e0edd85
  • actions/upload-artifact v4.3.6@834a144ee995460fba8ed112a2fc961b36a5ec5a
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/download-artifact v4.1.8@fa0a91b85d4f404e444e00e005971372dc801d16
  • docker/setup-buildx-action v3.6.1@988b5a0280414f521da01fcc63a27aeeb4b104db
  • docker/metadata-action v5.5.1@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
  • docker/login-action v3.3.0@9780b0c442fbb1117ed29e0efdff1e18412f7567
  • docker/login-action v3.3.0@9780b0c442fbb1117ed29e0efdff1e18412f7567
  • ubuntu 22.04
  • ubuntu 22.04
.github/workflows/registry-cleanup.yml
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • sredevopsorg/container-retention-policy 33db778efa5c55f6dfc556b3924c6098ffe7ee8c
.github/workflows/scorecards.yml
  • step-security/harden-runner v2.9.1@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde
  • actions/checkout v4.1.7@692973e3d937129bcbf40652eb9f2f61becf3332
  • ossf/scorecard-action v2.4.0@62b2cac7ed8198b15735ed49ab1e5cf35480ba46
  • actions/upload-artifact v4.3.6@834a144ee995460fba8ed112a2fc961b36a5ec5a
  • github/codeql-action v3.26.5@2c779ab0d087cd7fe7b826087247c2c81f27bfa6
.github/workflows/trivy.yaml
  • actions/checkout v4
  • aquasecurity/trivy-action 0.24.0
  • aquasecurity/trivy-action 0.24.0
  • github/codeql-action v3
  • actions/checkout v4
  • aquasecurity/trivy-action 0.24.0
  • actions/upload-artifact v4

  • Check this box to trigger a request for Renovate to run again on this repository

GKE + PersistentVolumes

I found that running this as is on GKE required the use of an initContainer to force the content folder to be nonroot. I believe GKE has some security context or preference that PVCs mounted in this manner are overwritten as root. Even setting it as readOnly: false would not change it.

This is what I added to my GKE deployment yaml, otherwise Ghost is unable to mkdir the content/settings directory on first run and is unable to edit items within the content/ folder.

template:
metadata:
namespace: ghostk3slocal
labels:
app: ghostk3s
spec:
volumes:
- name: ghostk3s-static-ghost
persistentVolumeClaim:
claimName: ghostk3s-static-ghost
- name: ghost-config-prod
secret:
secretName: ghost-config-prod
defaultMode: 420
- name: tmp
emptyDir:
sizeLimit: 128Mi
initContainers:
- name: chmod-er
image: busybox:latest
command:
- /bin/chown
- -R
- "1000:1000"
- /var/lib/ghost/content
volumeMounts:
- name: ghostk3s-static-ghost # or whatever
mountPath: /var/lib/ghost/content

containers:
- name: ghostk3s

arm64/armv7l support

Hello! Firstly, thanks for this!

The README says this:

Note: At this time, we dropped support for arm64 and armv7l, but we will add it back soon. Pull requests are welcome.

Can we have more information on what the blocker is for arm64?
That'll make it easier for people looking to contribute and serve as a progress report for users who are just interested in arm64 support

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.