├── .gitignore ├── LICENSE ├── README.md ├── action.yaml ├── docker-compose.yaml ├── ollama-entrypoint.sh └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | ollama/ 4 | open-webui/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Bitovi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deploy Ollama and Open WebUI with GitHub Actions 🌐🚀 2 | 3 | Welcome to the easiest way to get your own hosted private language model running swiftly with [Ollama](https://github.com/ollama/ollama) and [Open WebUI](https://github.com/open-webui/open-webui) 4 | 5 | ![alt text](screenshot.png) 6 | 7 | 8 | ## Why use this action? 9 | 10 | With this action, you can easily have your very own Large Language Model (LLM) like OpenAI's GPTChat or Anthropic's Claude. Except that it's entirely yours! You can tune it with your own data, and it's hosted on your own AWS account. 11 | 12 | This action is perfect for anyone who wants to try out the latest models, ask questions about documents, or even build Retrieval Augmented Generation (RAG) apps against an LLM you own! 13 | 14 | 🖥️✨ Get started below, and you'll be talking with your own hosted LLM in no time! 15 | 16 | Supported Cloud Providers: 17 | - AWS 18 | 19 | > **Note:** This action is currently in beta. Please report any issues you find by creating [an Issue](https://github.com/bitovi/github-actions-deploy-ollama/issues/new) or a [Pull Requests](https://github.com/bitovi/github-actions-deploy-ollama/pulls) 20 | 21 | 22 | ![alt](https://bitovi-gha-pixel-tracker-deployment-main.bitovi-sandbox.com/pixel/S-tGCZQT6JWtys19YCpQ_) 23 | 24 | 26 | 27 | 28 | ## Need help or have questions? 29 | This project is supported by [Bitovi DevOps](https://www.bitovi.com/devops-consulting) and a proud supporter of Open Source software. 30 | 31 | You can **get help or ask questions** on our [Discord channel](https://discord.gg/J7ejFsZnJ4)! Come hang out with us; We love discussing solutions! 32 | 33 | Or, you can hire us for training, consulting, development, and deployments (including LLM deployments ;). [Set up a free consultation](https://www.bitovi.com/devops-consulting). 34 | 35 | 36 | ## Requirements 37 | 38 | This is a list of requirements you'll need to meet in order to use this action. 39 | 1. An AWS account (yep, that's it!) 40 | 41 | 42 | ### 1. An AWS account 43 | You'll need [Access Keys](https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html) from an [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) 44 | 45 | 46 | ## Example usage 47 | 48 | 49 | 50 | 51 | Create `.github/workflow/deploy.yaml` with the following to build on push. 52 | 53 | ### Basic example 54 | ```yaml 55 | name: Basic deploy 56 | on: 57 | push: 58 | branches: [ main ] 59 | 60 | jobs: 61 | Ollama-Deploy: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - id: deploy 65 | name: Deploy 66 | uses: bitovi/github-actions-deploy-ollama@v0 67 | with: 68 | aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID}} 69 | aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} 70 | ``` 71 | 72 | > Once deployed, visit the url provided in the workflow output summary. 73 | 74 | 75 | ### Advanced example 76 | ```yaml 77 | name: Advanced deploy 78 | on: 79 | push: 80 | branches: [ main ] 81 | 82 | jobs: 83 | Ollama-Deploy: 84 | runs-on: ubuntu-latest 85 | steps: 86 | - id: deploy 87 | name: Deploy 88 | uses: bitovi/github-actions-deploy-ollama@v0 89 | with: 90 | disable_signup: true 91 | ollama_models: "phi3,qwen:0.5b,tinyllama" 92 | 93 | aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID}} 94 | aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} 95 | 96 | aws_r53_enable: true 97 | aws_r53_domain_name: bitovi.com 98 | aws_r53_sub_domain_name: ollama 99 | aws_r53_create_sub_cert: true 100 | 101 | ansible_start_docker_timeout: 9000 102 | 103 | env_ghs: ${{ secrets.DOT_ENV }} 104 | env_ghv: ${{ vars.VARS }} 105 | env_aws_secret: some-secret,some-other 106 | 107 | docker_cloudwatch_enable: true 108 | docker_cloudwatch_retention_days: 7 109 | ``` 110 | 111 | ## Customizing 112 | 113 | ### Inputs 114 | 1. [Deploy specifics](#deploy-specifics) 115 | 1. [AWS Specific](#aws-specific) 116 | 1. [GitHub Commons main inputs](#github-commons-main-inputs) 117 | 1. [Secrets and Environment Variables](#secrets-and-environment-variables-inputs) 118 | 1. [EC2](#ec2-inputs) 119 | 1. [VPC](#vpc-inputs) 120 | 1. [AWS Route53 Domains and Certificates](#aws-route53-domains-and-certificate-inputs) 121 | 1. [Docker](#docker-inputs) 122 | 123 | ### Outputs 124 | 1. [Action Outputs](#action-outputs) 125 | 126 | The following inputs can be used as `step.with` keys 127 |
128 |
129 | 130 | #### **Deploy Specific** 131 | | Name | Type | Description | 132 | |------------------|---------|------------------------------------| 133 | | `disable_signup` | Boolean | Disable user signup for the application. | 134 | | `ollama_models` | String | Comma separated list of models to download automatically. e.g. `"mistral,deepseek-r1,llama3.3"` [Check models](https://ollama.com/search) | 135 | 136 | #### **AWS Specific** 137 | | Name | Type | Description | 138 | |------------------|---------|------------------------------------| 139 | | `aws_access_key_id` | String | AWS access key ID | 140 | | `aws_secret_access_key` | String | AWS secret access key | 141 | | `aws_session_token` | String | AWS session token | 142 | | `aws_default_region` | String | AWS default region. Defaults to `us-east-1` | 143 | | `aws_resource_identifier` | String | Set to override the AWS resource identifier for the deployment. Defaults to `${GITHUB_ORG_NAME}-${GITHUB_REPO_NAME}-${GITHUB_BRANCH_NAME}`. | 144 | | `aws_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to all provisioned resources.| 145 |
146 |
147 | 148 | 149 | #### **GitHub Commons main inputs** 150 | | Name | Type | Description | 151 | |------------------|---------|------------------------------------| 152 | | `checkout` | Boolean | Set to `false` if the code is already checked out. (Default is `true`). | 153 | | `tf_stack_destroy` | Boolean | Set to `true` to destroy the stack - Will delete the `elb logs bucket` after the destroy action runs. | 154 | | `tf_state_file_name` | String | Change this to be anything you want to. Carefull to be consistent here. A missing file could trigger recreation, or stepping over destruction of non-defined objects. Defaults to `tf-state-aws`. | 155 | | `tf_state_file_name_append` | String | Appends a string to the tf-state-file. Setting this to `unique` will generate `tf-state-aws-unique`. (Can co-exist with `tf_state_file_name`) | 156 | | `tf_state_bucket` | String | AWS S3 bucket name to use for Terraform state. See [note](#s3-buckets-naming) | 157 | | `tf_state_bucket_destroy` | Boolean | Force purge and deletion of S3 bucket defined. Only evaluated when `tf_stack_destroy` is also `true`, so it is safe to leave this enabled when standing up your stack. Defaults to `false`. | 158 | | `ansible_ssh_to_private_ip` | Boolean | Make Ansible connect to the private IP of the instance. Only usefull if using a hosted runner in the same network. Default is `false`. | 159 | | `ansible_start_docker_timeout` | String | Ammount of time in seconds it takes Ansible to mark as failed the startup of docker. Defaults to `6000`.| 160 |
161 |
162 | 163 | #### **Secrets and Environment Variables Inputs** 164 | | Name | Type | Description - Check note about [**environment variables**](#environment-variables). | 165 | |------------------|---------|------------------------------------| 166 | | `env_aws_secret` | String | Secret name to pull env variables from AWS Secret Manager, could be a comma separated list, read in order. Expected JSON content. | 167 | | `env_repo` | String | File containing environment variables to be used with the app. | 168 | | `env_ghs` | String | `.env` file to be used with the app from Github secrets. | 169 | | `env_ghv` | String | `.env` file to be used with the app from Github variables. | 170 |
171 |
172 | 173 | #### **EC2 Inputs** 174 | | Name | Type | Description | 175 | |------------------|---------|------------------------------------| 176 | | `aws_ec2_instance_create` | Boolean | Toggles the creation of an EC2 instance. (Default is `true`). | 177 | | `aws_ec2_ami_filter` | String | AWS AMI Filter string. Will be used to lookup for lates image based on the string. Defaults to `ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*`.' | 178 | | `aws_ec2_ami_owner` | String | Owner of AWS AMI image. This ensures the provider is the one we are looking for. Defaults to `099720109477`, Canonical (Ubuntu). | 179 | | `aws_ec2_ami_id` | String | AWS AMI ID. Will default to the latest Ubuntu 22.04 server image (HVM). Accepts `ami-###` values. | 180 | | `aws_ec2_ami_update` | Boolean | Set this to `true` if you want to recreate the EC2 instance if there is a newer version of the AMI. Defaults to `false`.| 181 | | `aws_ec2_instance_type` | String | The AWS IAM instance type to use. Default is `inf1.xlarge`. See [this list](https://aws.amazon.com/ec2/instance-types/) for reference. | 182 | | `aws_ec2_instance_root_vol_size` | Integer | Define the volume size (in GiB) for the root volume on the AWS Instance. Defaults to `8`. | 183 | | `aws_ec2_instance_root_vol_preserve` | Boolean | Set this to true to avoid deletion of root volume on termination. Defaults to `false`. | 184 | | `aws_ec2_security_group_name` | String | The name of the EC2 security group. Defaults to `SG for ${aws_resource_identifier} - EC2`. | 185 | | `aws_ec2_iam_instance_profile` | String | The AWS IAM instance profile to use for the EC2 instance. Will create one if none provided with the name `aws_resource_identifier`. | 186 | | `aws_ec2_create_keypair_sm` | Boolean | Generates and manages a secret manager entry that contains the public and private keys created for the ec2 instance. | 187 | | `aws_ec2_instance_public_ip` | Boolean | Add a public IP to the instance or not. (Not an Elastic IP). Defaults to `true`. | 188 | | `aws_ec2_port_list` | String | Comma separated list of ports to be enabled in the EC2 instance security group. (NOT THE ELB) In a `80,443` format. Port `22` is enabled as default to allow Ansible connection. | 189 | | `aws_ec2_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to ec2 provisioned resources.| 190 |
191 |
192 | 193 | #### **VPC Inputs** 194 | | Name | Type | Description | 195 | |------------------|---------|------------------------------------| 196 | | `aws_vpc_create` | Boolean | Define if a VPC should be created. Defaults to `false`. | 197 | | `aws_vpc_name` | String | Define a name for the VPC. Defaults to `VPC for ${aws_resource_identifier}`. | 198 | | `aws_vpc_cidr_block` | String | Define Base CIDR block which is divided into subnet CIDR blocks. Defaults to `10.0.0.0/16`. | 199 | | `aws_vpc_public_subnets` | String | Comma separated list of public subnets. Defaults to `10.10.110.0/24`| 200 | | `aws_vpc_private_subnets` | String | Comma separated list of private subnets. If no input, no private subnet will be created. Defaults to ``. | 201 | | `aws_vpc_availability_zones` | String | Comma separated list of availability zones. Defaults to `aws_default_region+` value. If a list is defined, the first zone will be the one used for the EC2 instance. | 202 | | `aws_vpc_id` | String | **Existing** AWS VPC ID to use. Accepts `vpc-###` values. | 203 | | `aws_vpc_subnet_id` | String | **Existing** AWS VPC Subnet ID. If none provided, will pick one. (Ideal when there's only one). | 204 | | `aws_vpc_enable_nat_gateway` | Boolean | Adds a NAT gateway for each public subnet. Defaults to `false`. | 205 | | `aws_vpc_single_nat_gateway` | Boolean | Toggles only one NAT gateway for all of the public subnets. Defaults to `false`. | 206 | | `aws_vpc_external_nat_ip_ids` | String | **Existing** comma separated list of IP IDs if reusing. (ElasticIPs). | 207 | | `aws_vpc_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to vpc provisioned resources.| 208 |
209 |
210 | 211 | #### **AWS Route53 Domains and Certificate Inputss** 212 | | Name | Type | Description | 213 | |------------------|---------|------------------------------------| 214 | | `aws_r53_enable` | Boolean | Set this to true if you wish to use an existing AWS Route53 domain. **See note**. Default is `false`. | 215 | | `aws_r53_domain_name` | String | Define the root domain name for the application. e.g. bitovi.com'. | 216 | | `aws_r53_sub_domain_name` | String | Define the sub-domain part of the URL. Defaults to `aws_resource_identifier`. | 217 | | `aws_r53_root_domain_deploy` | Boolean | Deploy application to root domain. Will create root and www records. Default is `false`. | 218 | | `aws_r53_enable_cert` | Boolean | Set this to true if you wish to manage certificates through AWS Certificate Manager with Terraform. **See note**. Default is `false`. | 219 | | `aws_r53_cert_arn` | String | Define the certificate ARN to use for the application. **See note**. | 220 | | `aws_r53_create_root_cert` | Boolean | Generates and manage the root cert for the application. **See note**. Default is `false`. | 221 | | `aws_r53_create_sub_cert` | Boolean | Generates and manage the sub-domain certificate for the application. **See note**. Default is `false`. | 222 | | `aws_r53_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to R53 provisioned resources.| 223 |
224 |
225 | 226 | #### **Docker Inputs** 227 | | Name | Type | Description | 228 | |------------------|---------|------------------------------------| 229 | | `docker_install` | Boolean | Toggle docker installation through Ansible. `docker-compose up` will be excecuted after. Defaults to `true`. | 230 | | `docker_remove_orphans` | Boolean | Set to `true` to turn the `--remove-orphans` flag. Defaults to `false`. | 231 | | `docker_full_cleanup` | Boolean | Set to `true` to run `docker-compose down` and `docker system prune --all --force --volumes` after. Runs before `docker_install`. WARNING: docker volumes will be destroyed. | 232 | | `docker_repo_app_directory_cleanup` | Boolean | Will generate a timestamped compressed file (in the home directory of the instance) and delete the app repo directory. Runs before `docker_install` and after `docker_full_cleanup`. | 233 | | `docker_cloudwatch_enable` | Boolean | Toggle cloudwatch creation for Docker. Create a file named `docker-daemon.json` in your repo root dir if you need to customize it. Defaults to `false`. Check [docker docs](https://docs.docker.com/config/containers/logging/awslogs/).| 234 | | `docker_cloudwatch_lg_name` | String| Log group name. Will default to `${aws_resource_identifier}-docker-logs` if none. | 235 | | `docker_cloudwatch_skip_destroy` | Boolean | Toggle deletion or not when destroying the stack. Defaults to `false`. | 236 | | `docker_cloudwatch_retention_days` | String | Number of days to retain logs. 0 to never expire. Defaults to `14`. See [note](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group#retention_in_days). | 237 |
238 |
239 | 240 | #### **Action Outputs** 241 | | Name | Description | 242 | |------------------|------------------------------------| 243 | | **VPC** | 244 | | `aws_vpc_id` | The selected VPC ID used. | 245 | | **EC2** | 246 | | `vm_url` | The URL of the generated app. | 247 | | `instance_endpoint` | The URL of the generated ec2 instance. | 248 | | `ec2_sg_id` | SG ID for the EC2 instance. | 249 |
250 |
251 | 252 | 253 | ## Environment variables 254 | 255 | For envirnoment variables in your app, you can provide: 256 | - `env_repo` - A file in your repo that contains env vars 257 | - `env_ghv` - An entry in [Github actions variables](https://docs.github.com/en/actions/learn-github-actions/variables) 258 | - `env_ghs` - An entry in [Github secrets](https://docs.github.com/es/actions/security-guides/encrypted-secrets) 259 | - `env_aws_secret` - The path to a JSON format secret in AWS 260 | 261 | These environment variables are merged (in the following order) to the .env file and provided to both the Prometheus and Grafana services: 262 | - Terraform passed env vars ( This is not optional nor customizable ) 263 | - Repository checked-in env vars - repo_env file as default. (KEY=VALUE style) 264 | - Github Secret - Create a secret named DOT_ENV - (KEY=VALUE style) 265 | - AWS Secret - JSON style like '{"key":"value"}' 266 | 267 | ## Note about resource identifiers 268 | 269 | Most resources will contain the tag `${GITHUB_ORG_NAME}-${GITHUB_REPO_NAME}-${GITHUB_BRANCH_NAME}`, some of them, even the resource name after. 270 | We limit this to a 60 characters string because some AWS resources have a length limit and short it if needed. 271 | 272 | We use the kubernetes style for this. For example, kubernetes -> k(# of characters)s -> k8s. And so you might see some compressions are made. 273 | 274 | For some specific resources, we have a 32 characters limit. If the identifier length exceeds this number after compression, we remove the middle part and replace it for a hash made up from the string itself. 275 | 276 | ### S3 buckets naming 277 | 278 | Buckets names can be made of up to 63 characters. If the length allows us to add -tf-state, we will do so. If not, a simple -tf will be added. 279 | 280 | ## CERTIFICATES - Only for AWS Managed domains with Route53 281 | 282 | As a default, the application will be deployed and the ELB public URL will be displayed. 283 | 284 | If `aws_r53_enable` and `aws_r53_enable_cert` are true, we will look up for a certificate with the name of the domain defined in `aws_r53_domain_name`. (eg. `example.com`). We expect that certificate to contain both `example.com` and `*.example.com` in the defined region. 285 | 286 | Setting `aws_r53_create_root_cert` to `true` will create this certificate with both `example.com` and `*.example.com` for you, and validate them. (DNS validation). 287 | 288 | Setting `aws_r53_create_sub_cert` to `true` will create a certificate **just for the subdomain**, and validate it. 289 | 290 | > :warning: Be very careful here! **Created certificates are fully managed by Terraform**. Therefor **they will be destroyed upon stack destruction**. 291 | 292 | To change a certificate (root_cert, sub_cert, ARN or pre-existing root cert), you must first toogle `aws_r53_enable_cert` to false, run the action, and then set the `aws_r53_enable_cert` flag to true, add the desired settings and excecute the action again. (**This will destroy the first certificate.**) 293 | 294 | This is necessary due to a limitation that prevents certificates from being changed while in use by certain resources. 295 | 296 | ## Made with BitOps 297 | [BitOps](https://bitops.sh) allows you to define Infrastructure-as-Code for multiple tools in a central place. This action uses a BitOps [Operations Repository](https://bitops.sh/operations-repo-structure/) to set up the necessary Terraform and Ansible to create infrastructure and deploy to it. 298 | 299 | ## Contributing 300 | We would love for you to contribute to [bitovi/github-actions-deploy-ollama](https://github.com/bitovi/github-actions-deploy-ollama) and help make it even better than it is today! 301 | 302 | Would you like to see additional features? [Create an issue](https://github.com/bitovi/github-actions-deploy-ollama/issues/new) or a [Pull Requests](https://github.com/bitovi/github-actions-deploy-ollama/pulls). 303 | 304 | ## License 305 | The scripts and documentation in this project are released under the [MIT License](https://github.com/bitovi/github-actions-deploy-ollama/blob/main/LICENSE). 306 | -------------------------------------------------------------------------------- /action.yaml: -------------------------------------------------------------------------------- 1 | name: 'Deploy Ollama and Open WebUI' 2 | description: 'Deploy your own LLM (large language model) like llama3 with a web based UI to an EC2 instance.' 3 | branding: 4 | icon: upload-cloud 5 | color: red 6 | 7 | inputs: 8 | # App specific config 9 | disable_signup: 10 | description: 'Disable user signup for the application.' 11 | required: false 12 | default: 'false' 13 | ollama_models: 14 | description: 'Comma separated list of models to download automatically.' 15 | required: false 16 | default: '' 17 | 18 | # AWS Configuration 19 | aws_access_key_id: 20 | description: 'AWS access key ID' 21 | required: false 22 | aws_secret_access_key: 23 | description: 'AWS secret access key' 24 | required: false 25 | aws_session_token: 26 | description: 'AWS session token' 27 | required: false 28 | aws_default_region: 29 | description: 'AWS default region' 30 | default: us-east-1 31 | required: false 32 | aws_resource_identifier: 33 | description: 'Set to override the AWS resource identifier for the deployment. Defaults to `${org}-{repo}-{branch}`. Use with destroy to destroy specific resources.' 34 | required: false 35 | aws_additional_tags: # additional_tags 36 | description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' 37 | required: false 38 | 39 | # GitHub Commons main inputs 40 | checkout: 41 | description: 'Specifies if this action should checkout the code' 42 | required: false 43 | default: 'true' 44 | tf_stack_destroy: 45 | description: 'Whether to destroy the Terraform stack on completion.' 46 | required: false 47 | default: 'false' 48 | tf_state_file_name: 49 | description: 'The name of the Terraform state file.' 50 | required: true 51 | tf_state_file_name_append: 52 | description: 'Append a suffix to the state file name to allow unique state management.' 53 | required: false 54 | tf_state_bucket: 55 | description: 'The S3 bucket name where the Terraform state file will be stored.' 56 | required: true 57 | tf_state_bucket_destroy: 58 | description: 'Whether to destroy the state bucket on stack completion.' 59 | required: false 60 | ansible_ssh_to_private_ip: 61 | description: 'Make Ansible connect to the private IP of the instance. Only usefull if using a hosted runner in the same network.' 62 | required: false 63 | ansible_start_docker_timeout: 64 | description: 'Ammount of time in seconds it takes Ansible to mark as failed the startup of docker. Defaults to `300`' 65 | required: false 66 | 67 | # ENV files 68 | env_aws_secret: 69 | description: 'Secret name to pull env variables from AWS Secret Manager, could be a comma separated list, read in order. Expected JSON content.' 70 | required: false 71 | env_repo: 72 | description: 'File containing environment variables to be used with the app' 73 | required: false 74 | env_ghs: 75 | description: 'GitHub Secret Name containing `.env` file style to be used with the app.' 76 | required: false 77 | env_ghv: 78 | description: 'GitHub Variable Name containing `.env` file style to be used with the app.' 79 | required: false 80 | 81 | # EC2 Instance config 82 | aws_ec2_instance_create: 83 | description: 'Define if an EC2 instance should be created' 84 | required: false 85 | default: true 86 | aws_ec2_ami_filter: 87 | description: 'AWS AMI Filter string. Will be used to lookup for lates image based on the string. Defaults to `ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*`.' 88 | required: false 89 | aws_ec2_ami_owner: 90 | description: 'Owner of AWS AMI image. This ensures the provider is the one we are looking for. Defaults to `099720109477`, Canonical (Ubuntu).' 91 | required: false 92 | aws_ec2_ami_id: # aws_ami_id 93 | description: 'AWS AMI ID. Will default to lookup for latest image of the `aws_ec2_ami_filter` string. This will override `aws_ec2_ami_filter` lookup.' 94 | required: false 95 | aws_ec2_ami_update: 96 | description: 'Set this to true if you want to recreate the EC2 instance if there is a newer version of the AMI.' 97 | required: false 98 | aws_ec2_iam_instance_profile: # aws_ec2_instance_profile 99 | description: 'The AWS IAM instance profile to use for the EC2 instance' 100 | required: false 101 | aws_ec2_instance_type: 102 | description: 'Type of AWS EC2 instance to deploy.' 103 | required: false 104 | default: 'inf1.xlarge' 105 | aws_ec2_instance_root_vol_size: # ec2_volume_size 106 | description: 'Define the volume size (in GiB) for the root volume on the AWS Instance.' 107 | default: '20' 108 | required: false 109 | aws_ec2_instance_root_vol_preserve: 110 | description: 'Set this to true to avoid deletion of root volume on termination. Defaults to false.' 111 | required: false 112 | aws_ec2_security_group_name: 113 | description: 'The name of the EC2 security group' 114 | required: false 115 | aws_ec2_create_keypair_sm: 116 | description: 'Create a key pair using AWS Secrets Manager.' 117 | required: false 118 | aws_ec2_instance_public_ip: 119 | description: 'Add a public IP to the instance or not. (Not an Elastic IP)' 120 | required: false 121 | default: true 122 | aws_ec2_port_list: 123 | description: 'List of ports to be enabled as an ingress rule in the EC2 SG, in a [xx,yy] format - Not the ELB' 124 | required: false 125 | aws_ec2_additional_tags: 126 | description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' 127 | required: false 128 | 129 | # AWS VPC Inputs 130 | aws_vpc_create: 131 | description: 'Define if a VPC should be created' 132 | required: false 133 | aws_vpc_name: 134 | description: 'Set a specific name for the VPC' 135 | required: false 136 | aws_vpc_cidr_block: 137 | description: 'Define Base CIDR block which is divided into subnet CIDR blocks. Defaults to 10.0.0.0/16.' 138 | required: false 139 | aws_vpc_public_subnets: 140 | description: 'Comma separated list of public subnets. Defaults to 10.10.110.0/24' 141 | required: false 142 | aws_vpc_private_subnets: 143 | description: 'Comma separated list of private subnets. If none, none will be created.' 144 | required: false 145 | aws_vpc_availability_zones: 146 | description: 'Comma separated list of availability zones. Defaults to `aws_default_region.' 147 | required: false 148 | aws_vpc_id: 149 | description: 'AWS VPC ID. Accepts `vpc-###` values.' 150 | required: false 151 | aws_vpc_subnet_id: 152 | description: 'Specify a Subnet to be used with the instance. If none provided, will pick one.' 153 | required: false 154 | aws_vpc_enable_nat_gateway: 155 | description: 'Enables NAT gateway' 156 | required: false 157 | aws_vpc_single_nat_gateway: 158 | description: 'Creates only one NAT gateway' 159 | required: false 160 | aws_vpc_external_nat_ip_ids: 161 | description: 'Comma separated list of IP IDS to reuse in the NAT gateways' 162 | required: false 163 | aws_vpc_additional_tags: 164 | description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' 165 | required: false 166 | 167 | # AWS Route53 Domains abd Certificates 168 | aws_r53_enable: 169 | description: 'Enables the usage of Route53 to manage DNS records.' 170 | required: false 171 | aws_r53_domain_name: # domain_name 172 | description: 'Define the root domain name for the application. e.g. app.com' 173 | required: false 174 | aws_r53_sub_domain_name: # sub_domain 175 | description: 'Define the sub-domain part of the URL. Defaults to `${org}-${repo}-{branch}`' 176 | aws_r53_root_domain_deploy: # root_domain 177 | description: 'Deploy to root domain. Will generate two DNS recrods, one for root, another for www' 178 | required: false 179 | aws_r53_enable_cert: # no_cert 180 | description: 'Makes the application use a certificate by enabling a certificate lookup.' 181 | required: false 182 | default: true # Legacy enable 183 | aws_r53_cert_arn: # cert_arn 184 | description: 'Define the certificate ARN to use for the application' 185 | required: false 186 | aws_r53_create_root_cert: # create_root_cert 187 | description: 'Generates and manage the root cert for the application' 188 | required: false 189 | aws_r53_create_sub_cert: # create_sub_cert 190 | description: 'Generates and manage the sub-domain certificate for the application' 191 | required: false 192 | aws_r53_additional_tags: 193 | description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' 194 | required: false 195 | 196 | # Docker 197 | docker_install: 198 | description: 'Define if docker should be installed. After this, docker-compose up will be excecuted.' 199 | required: false 200 | default: true 201 | docker_remove_orphans: 202 | description: 'Toggle --remove-orphans flag. Defaults to false.' 203 | required: false 204 | docker_full_cleanup: 205 | description: 'Set to true to run docker-compose down and docker system prune --all --force --volumes after.' 206 | required: false 207 | docker_repo_app_directory_cleanup: # app_directory_cleanup 208 | description: 'Will generate a timestamped compressed file and delete the app repo directory.' 209 | required: false 210 | docker_cloudwatch_enable: 211 | description: 'Toggle cloudwatch creation for Docker containers.' 212 | required: false 213 | default: true 214 | docker_cloudwatch_lg_name: 215 | description: 'Log group name. Will default to aws_identifier if none.' 216 | required: false 217 | docker_cloudwatch_skip_destroy: 218 | description: 'Toggle deletion or not when destroying the stack.' 219 | required: false 220 | docker_cloudwatch_retention_days: 221 | description: 'Number of days to retain logs. 0 to never expire.' 222 | required: false 223 | 224 | outputs: 225 | # VPC 226 | aws_vpc_id: 227 | description: "The selected VPC ID used." 228 | value: ${{ steps.deploy.outputs.aws_vpc_id }} 229 | # EC2 230 | vm_url: 231 | description: "The URL of the generated app" 232 | value: ${{ steps.deploy.outputs.vm_url }} 233 | instance_endpoint: 234 | description: "The URL of the generated ec2 instance" 235 | value: ${{ steps.deploy.outputs.instance_endpoint }} 236 | ec2_sg_id: 237 | description: "SG ID for the EC2 instance" 238 | value: ${{ steps.deploy.outputs.ec2_sg_id }} 239 | 240 | runs: 241 | using: 'composite' 242 | steps: 243 | - name: Checkout if required 244 | if: ${{ inputs.checkout == 'true' }} 245 | uses: actions/checkout@v4 246 | 247 | - name: Copy Deployment Config 248 | shell: bash 249 | env: 250 | GITHUB_ACTION_PATH: ${{ github.action_path }} 251 | APP_SUBDIR: gha-deployment 252 | run: | 253 | app_path=$GITHUB_WORKSPACE/$APP_SUBDIR 254 | 255 | echo "Copying app Repo" 256 | mkdir -p "$app_path" 257 | cp -r $GITHUB_ACTION_PATH/. "$app_path" 258 | 259 | echo "removing operations dir" 260 | rm -rf $app_path/operations 261 | 262 | # if inputs.disable_signup == 'true, then remove the signup page 263 | # env var: ENABLE_SIGNUP=false 264 | - name: Set app env config 265 | id: set-app-env-config 266 | shell: bash 267 | env: 268 | GITHUB_ACTION_PATH: ${{ github.action_path }} 269 | APP_SUBDIR: gha-deployment 270 | DISABLE_SIGNUP: ${{ inputs.disable_signup }} 271 | OLLAMA_MODELS: ${{ inputs.ollama_models }} 272 | run: | 273 | app_path=$GITHUB_WORKSPACE/$APP_SUBDIR 274 | filename="auto.gha.app.env" 275 | 276 | echo "::group::Set up GHA generated app config" 277 | echo "checking if disable_signup is set ($DISABLE_SIGNUP)" 278 | if [ "$DISABLE_SIGNUP" == "true" ]; then 279 | echo "Disabling signup" 280 | echo "ENABLE_SIGNUP=false" >> $app_path/$filename 281 | else 282 | echo "Enabling signup" 283 | echo "ENABLE_SIGNUP=true" >> $app_path/$filename 284 | fi 285 | 286 | echo "checking if ollama_models is not empty and has more than one character." 287 | if [[ -n "$OLLAMA_MODELS" && ${#OLLAMA_MODELS} -gt 1 ]]; then 288 | echo "MODELS=$OLLAMA_MODELS" >> $app_path/$filename 289 | fi 290 | echo "" 291 | echo "::endgroup::" 292 | 293 | - name: Deploy with Bitovi Commons 294 | id: deploy 295 | uses: bitovi/github-actions-commons@v1 296 | with: 297 | # AWS Specific 298 | aws_access_key_id: ${{ inputs.aws_access_key_id }} 299 | aws_secret_access_key: ${{ inputs.aws_secret_access_key }} 300 | aws_session_token: ${{ inputs.aws_session_token }} 301 | aws_default_region: ${{ inputs.aws_default_region }} 302 | aws_resource_identifier: ${{ inputs.aws_resource_identifier }} 303 | aws_additional_tags: ${{ inputs.aws_additional_tags }} 304 | 305 | # Action main inputs 306 | gh_action_repo: ${{ github.action_path }} 307 | checkout: false 308 | tf_stack_destroy: ${{ inputs.tf_stack_destroy }} 309 | tf_state_file_name: ${{ inputs.tf_state_file_name }} 310 | tf_state_file_name_append: ${{ inputs.tf_state_file_name_append }} 311 | tf_state_bucket: ${{ inputs.tf_state_bucket }} 312 | tf_state_bucket_destroy: ${{ inputs.tf_state_bucket_destroy }} 313 | ansible_ssh_to_private_ip: ${{ inputs.ansible_ssh_to_private_ip }} 314 | ansible_start_docker_timeout: ${{ inputs.ansible_start_docker_timeout }} 315 | 316 | # ENV files 317 | env_aws_secret: ${{ inputs.env_aws_secret }} 318 | env_repo: ${{ inputs.env_repo }} 319 | env_ghs: ${{ inputs.env_ghs }} 320 | env_ghv: ${{ inputs.env_ghv }} 321 | 322 | # EC2 323 | aws_ec2_instance_create: ${{ inputs.aws_ec2_instance_create }} 324 | aws_ec2_ami_filter: ${{ inputs.aws_ec2_ami_filter }} 325 | aws_ec2_ami_owner: ${{ inputs.aws_ec2_ami_owner }} 326 | aws_ec2_ami_id: ${{ inputs.aws_ec2_ami_id || inputs.aws_ami_id }} 327 | aws_ec2_ami_update: ${{ inputs. aws_ec2_ami_update }} 328 | aws_ec2_iam_instance_profile: ${{ inputs.aws_ec2_iam_instance_profile || inputs.ec2_instance_profile }} 329 | aws_ec2_instance_type : ${{ inputs.aws_ec2_instance_type || inputs.ec2_instance_type }} 330 | aws_ec2_instance_root_vol_size: ${{ inputs.aws_ec2_instance_root_vol_size || inputs.ec2_volume_size }} 331 | aws_ec2_instance_root_vol_preserve: ${{ inputs.aws_ec2_instance_root_vol_preserve }} 332 | aws_ec2_security_group_name: ${{ inputs.aws_ec2_security_group_name }} 333 | aws_ec2_create_keypair_sm: ${{ inputs.aws_ec2_create_keypair_sm || inputs.create_keypair_sm_entry }} 334 | aws_ec2_instance_public_ip: ${{ inputs.aws_ec2_instance_public_ip }} 335 | aws_ec2_port_list: ${{ inputs.aws_ec2_port_list }} 336 | aws_ec2_user_data_file: ${{ inputs.aws_ec2_user_data_file }} 337 | aws_ec2_user_data_replace_on_change: ${{ inputs.aws_ec2_user_data_replace_on_change }} 338 | aws_ec2_additional_tags: ${{ inputs.aws_ec2_additional_tags }} 339 | # aws_ec2_port_list: "3011,11434" # Only usefull if exposing instance ports 340 | 341 | ## AWS VPC 342 | aws_vpc_create: ${{ inputs.aws_vpc_create }} 343 | aws_vpc_name: ${{ inputs.aws_vpc_name }} 344 | aws_vpc_cidr_block: ${{ inputs.aws_vpc_cidr_block }} 345 | aws_vpc_public_subnets: ${{ inputs.aws_vpc_public_subnets }} 346 | aws_vpc_private_subnets: ${{ inputs.aws_vpc_private_subnets }} 347 | aws_vpc_availability_zones: ${{ inputs.aws_vpc_availability_zones }} 348 | aws_vpc_id: ${{ inputs.aws_vpc_id }} 349 | aws_vpc_subnet_id: ${{ inputs.aws_vpc_subnet_id }} 350 | aws_vpc_enable_nat_gateway: ${{ inputs.aws_vpc_enable_nat_gateway }} 351 | aws_vpc_single_nat_gateway: ${{ inputs.aws_vpc_single_nat_gateway }} 352 | aws_vpc_external_nat_ip_ids: ${{ inputs.aws_vpc_external_nat_ip_ids }} 353 | aws_vpc_additional_tags: ${{ inputs.aws_vpc_additional_tags }} 354 | 355 | # AWS Route53 Domains abd Certificates 356 | aws_r53_enable: ${{ inputs.aws_r53_enable }} 357 | aws_r53_domain_name: ${{ inputs.aws_r53_domain_name || inputs.domain_name }} 358 | aws_r53_sub_domain_name: ${{ inputs.aws_r53_sub_domain_name || inputs.sub_domain }} 359 | aws_r53_root_domain_deploy: ${{ inputs.aws_r53_root_domain_deploy || inputs.root_domain }} 360 | aws_r53_enable_cert: ${{ inputs.aws_r53_enable_cert }} 361 | aws_r53_cert_arn: ${{ inputs.aws_r53_cert_arn || inputs.cert_arn }} 362 | aws_r53_create_root_cert: ${{ inputs.aws_r53_create_root_cert || inputs.create_root_cert }} 363 | aws_r53_create_sub_cert: ${{ inputs.aws_r53_create_sub_cert || inputs.create_sub_cert }} 364 | aws_r53_additional_tags: ${{ inputs.aws_r53_additional_tags }} 365 | 366 | # Docker 367 | docker_install: ${{ inputs.docker_install }} 368 | docker_remove_orphans: ${{ inputs.docker_remove_orphans }} 369 | docker_full_cleanup: ${{ inputs.docker_full_cleanup }} 370 | docker_repo_app_directory: 'gha-deployment' 371 | docker_repo_app_directory_cleanup: ${{ inputs.docker_repo_app_directory_cleanup || inputs.app_directory_cleanup }} 372 | docker_cloudwatch_enable: ${{ inputs.docker_cloudwatch_enable }} 373 | docker_cloudwatch_lg_name: ${{ inputs.docker_cloudwatch_lg_name }} 374 | docker_cloudwatch_skip_destroy: ${{ inputs.docker_cloudwatch_skip_destroy }} 375 | docker_cloudwatch_retention_days: ${{ inputs.docker_cloudwatch_retention_days }} 376 | 377 | # AWS ELB 378 | aws_elb_create: true 379 | aws_elb_listen_port: "3011,11434" 380 | aws_elb_app_port: "3011,11434" 381 | aws_elb_healthcheck: "TCP:11434" 382 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.0" 2 | services: 3 | 4 | ui: 5 | image: ghcr.io/open-webui/open-webui:main 6 | restart: always 7 | ports: 8 | - 3011:8080 9 | volumes: 10 | - ./open-webui:/app/backend/data 11 | env_file: 12 | - .env 13 | - auto.gha.app.env 14 | environment: 15 | # - "ENABLE_SIGNUP=false" 16 | - "OLLAMA_BASE_URL=http://ollama:11434" 17 | depends_on: 18 | - ollama 19 | 20 | # ollama which should have a start command of `ollama run llama3:8b` 21 | ollama: 22 | image: ollama/ollama 23 | container_name: ollama 24 | restart: always 25 | ports: 26 | - 11434:11434 27 | env_file: 28 | - .env 29 | - auto.gha.app.env 30 | volumes: 31 | - ./ollama:/root/.ollama 32 | - ./ollama-entrypoint.sh:/ollama-entrypoint.sh # Mount script inside container 33 | entrypoint: ["/bin/sh", "/ollama-entrypoint.sh"] # Use custom entrypoint -------------------------------------------------------------------------------- /ollama-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | #MODELS="mistral" # Change this to your models (comma-separated) 5 | 6 | # Start Ollama in the background 7 | ollama serve & 8 | 9 | # Wait a few seconds to ensure the server is up 10 | sleep 5 11 | 12 | # Convert MODELS into an array and loop through each model 13 | IFS=',' # Set comma as the delimiter 14 | for MODEL in $MODELS; do 15 | MODEL=$(echo "$MODEL" | xargs) # Trim spaces 16 | if ! ollama list | grep -q "$MODEL"; then 17 | echo "Model $MODEL not found, pulling it now..." 18 | ollama pull "$MODEL" 19 | else 20 | echo "Model $MODEL is already available." 21 | fi 22 | done 23 | 24 | # Bring Ollama back to the foreground 25 | wait -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/github-actions-deploy-ollama/8f8149c4e81f93e93e8f591264083d611870871f/screenshot.png --------------------------------------------------------------------------------