This project demonstrates a practical approach to deploying multiple frontend applications from an Nx workspace to Raspberry Pi using Docker containers and Cloudflare. It maximizes the benefits of Nx's affected features for optimized CI/CD pipelines. The deployment process is automated using GitHub Actions workflows, which leverage Nx's dependency graph to only build, test, and deploy affected projects, resulting in fast and efficient deployments. The deployment to Raspberry Pi is performed via a secure Cloudflare tunnel and SSH, with Docker containers managed by a robust deployment script.
This is an example implementation of my deployment strategy that combines:
- Nx monorepo for efficient multi-app development
- Docker containerization for consistent deployment
- Nginx for serving multiple apps from a single container
- GitHub Actions for automated CI/CD with Nx affected commands
- Raspberry Pi as the target production environment
WARNING: I tested this solution only on react/vue/angular apps. For metaframeworks I am almost 100% sure it will not work out of the box, basing of how I seted up the Dockerfiles and Nginx configuration. You may need to adjust the Dockerfiles and Nginx configuration to suit your specific applications.
Note: This approach works well for my use case, though it may not be the "perfect" solution. I'm open to improvements and may update this in the future.
This project uses several GitHub Actions workflows to automate validation, type generation, deployment, and code quality checks for the Nx monorepo. The workflows are designed to maximize speed by leveraging Nx's affected feature, so only changed projects are built and tested.
- Runs on: Every pull request to
mainand on workflow calls. - Purpose:
- Installs dependencies
- Finds the last successful deploy SHA
- Runs
nx affectedfor lint, build, and test on only changed projects - Ensures only relevant code is checked, speeding up PR validation
-
deploy.yml:- Runs on: Every push to
main - Purpose:
- Replaces dev URLs with production URLs in environment files
- Installs dependencies
- Finds the last successful deploy SHA
- Runs
nx affectedfor lint, build, and test on only changed projects - Builds and pushes Docker images for affected apps (e.g., app-1, app-2) to GHCR, only if their build output exists (THIS IS THE KEY PART)
- Triggers deployment to Raspberry Pi via
deploy-to-production.yml
- Runs on: Every push to
-
deploy-to-production.yml:- Purpose:
- Sets up Cloudflare tunnel and SSH configuration
- Copies the
deploy.shscript to the Raspberry Pi - Executes the deployment script remotely, which pulls and runs the latest Docker images for all apps
- Cleans up SSH keys and deployment script after deployment
- Provides a deployment summary and troubleshooting tips
- Purpose:
- Location: Root of repository
- Purpose: Handles the actual deployment of all apps on the Raspberry Pi. This script is copied and executed remotely by the
deploy-to-production.ymlworkflow. - How it works:
- Requires
GITHUB_TOKENandGITHUB_ACTORenvironment variables for authentication with GitHub Container Registry (GHCR). - Logs in to GHCR using the provided credentials.
- Defines all apps to deploy with their respective ports.
- For each app:
- Stops and removes any existing container for the app
- Prunes old Docker images
- Pulls the latest image from GHCR
- Runs the container on the correct port with restart policy
- Performs a health check to ensure the container is running
- If any container fails to start, logs are shown and the script exits with an error.
- If all succeed, prints a success message.
- Requires
Note: The script is designed for a single-node deployment (Raspberry Pi) and expects Docker to be installed and running. It is robust against missing containers/images and will always attempt to deploy the latest version of each app.
- All workflows use Node.js 20 and npm for dependency management.
- Docker images are built for
linux/arm64and pushed to GitHub Container Registry (GHCR). - Deployment is designed for a Raspberry Pi using Docker and Cloudflare Tunnel for secure SSH access.
The pipeline leverages Nx's affected commands to:
- Only build/test/lint projects that have changed or depend on changed projects
- Reduce CI/CD time by skipping unnecessary work
- Maintain confidence by testing all affected dependencies
# Example: Only lint/test/build what's affected since last successful deployment
npx nx affected --targets=lint,build,test --base=<last-successful-sha> --head=HEAD- Shared libraries between applications
- Consistent tooling (linting, testing, building)
- Dependency graph understanding for optimal builds (nx affected)
- Code generation for consistent project structure
- Environment consistency from development to production
- Easy deployment to any Docker-supported platform
- Resource isolation and scaling capabilities
- Affected-only builds reduce pipeline time
- Automatic testing of dependency chains
- Container registry integration with GHCR
- Nginx optimization for static file serving
- Multi-app support in single container
- Health checks and monitoring capabilities
- Raspberry Pi with Docker installed
- Cloudflare Tunnel set up for remote access
- Make sure you are using main as the default branch in your repository. If you use master or other named branches, you may need to adjust the workflows accordingly.
- Fork or clone the repository.
- Set up the following GitHub Actions secrets in your repository:
CLOUDFLARE_TUNNEL_DOMAIN: The domain for your Cloudflare tunnelRASPBERRY_PI_USERNAME: The SSH username for your Raspberry PiRASPBERRY_PI_SSH_KEY: The private SSH key for your Raspberry PiSSH_KNOWN_HOSTS: The known hosts file from your Raspberry Pi
- Push changes to the repository to trigger the CI/CD pipeline in GitHub Actions.
GitHub Actions Package Publishing Issues
If you encounter the error:
buildx failed with: ERROR: failed to build: failed to solve: failed to push ghcr.io/<owner>/<repo>/nx-multi-app:latest: denied: installation not allowed to Create organization package
Solution: Enable Package Permissions
- Go to your repository settings:
- Navigate to
https://github.com/<owner>/<repo>/settings - Go to
Actions → General - Scroll down to "Workflow permissions"
- Select "Read and write permissions"
- Check "Allow GitHub Actions to create and approve pull requests"
- Save the changes
- Navigate to
- Node.js 20+
- npm
- Docker (for containerization)
- Docker Compose (for local development)
-
Clone the repository
git clone <repository-url> cd nx-docker-rpi-deployment
-
Install dependencies
npm ci
-
Start development servers
# Start both apps in development mode npx nx serve app-1 npx nx serve app-2 # Or run in parallel npx nx run-many --target=serve --projects=app-1,app-2
# Local development
docker-compose up --buildWhen using this example for your own Nx workspace, you may need to adjust the following:
- Modify Dockerfiles
- Update the images of apps if needed on
apps/<app-name>/Dockerfile
- Update the images of apps if needed on
- Update Nginx Configuration
- Modify
apps/<app-name>/nginx/<app-name>.confto include your applications
- Modify
- Adjust GitHub Actions Workflows
- Update workflow files in
.github/workflows/to match your repository and application structure - In
.github/workflows/README.md, you can find info about how to add new app to the deployment process - Add additional checks or remove unnecessary ones from the
checks.ymlfile - Update image names in Docker build steps to match your project
- Adjust platforms if you need to support different architectures (e.g.
linux/amd64,linux/arm64). In this example, deployment targets Raspberry Pi (linux/arm64). - When using the
deploy-to-production.yml, use the images built by the main workflow
- Update workflow files in
- Update Deployment Script
- Modify
deploy.shto suit your deployment needs - Adjust repo and owner variables
- Modify
Edit the deploy-to-production.yml workflow file to suit your target environment. Remember to use images built by the main workflow for deployment.
This is a personal example project, but suggestions and improvements are welcome! Feel free to:
- Open issues for questions or suggestions
- Submit pull requests for improvements
- Share your own deployment strategies
This project is licensed under the MIT License - see the LICENSE file for details.
Note: This approach represents my current deployment strategy and has proven effective for my use cases. While it may not be the optimal solution for every scenario, it successfully combines Nx's powerful affected features with Docker's containerization benefits for efficient CI/CD pipelines.