Photo OpenTofu GitHub Actions cloud automation

Automating Cloud Infrastructure Provisioning Using OpenTofu and GitHub Actions

Why Automate Cloud Provisioning?

Automating cloud infrastructure provisioning is essential for speed, consistency, and error reduction. Manually setting up cloud resources like virtual machines, databases, or networking components is time-consuming, prone to human error, and difficult to scale. Imagine spinning up dozens of identical environments for development, testing, and production; doing that by hand is a nightmare. Automation, using tools like OpenTofu and orchestrated by processes like GitHub Actions, allows you to define your infrastructure as code (IaC), making it versionable, repeatable, and auditable. This means your infrastructure can be deployed and managed with the same rigor as your application code, leading to faster deployments, more reliable environments, and a significant reduction in operational overhead. It shifts the focus from repetitive, manual tasks to strategic infrastructure design and optimization.

In the realm of cloud infrastructure management, the article on automating cloud infrastructure provisioning using OpenTofu and GitHub Actions presents a cutting-edge approach to streamline deployment processes. For those interested in enhancing their design capabilities alongside their technical skills, you might find the article on logo design software particularly useful. It discusses various tools that can complement your cloud projects by providing visually appealing branding solutions. You can read more about it here: Discover the Best Software for Logo Design Today.

Key Takeaways

  • The training data includes information and events up to October 2023.
  • Insights and knowledge are based on a wide range of sources available until the cutoff date.
  • No updates or developments occurring after October 2023 are included in the training.
  • Users should verify current information from reliable sources for the latest updates.
  • The model’s responses reflect the context and knowledge available up to the specified date.

OpenTofu: Your Infrastructure as Code Foundation

OpenTofu is an open-source, community-driven fork of Terraform. For those familiar with Terraform, OpenTofu will feel immediately comfortable; it maintains full compatibility with existing Terraform configurations and state files up to its branching point. This means you can often transition existing Terraform projects to OpenTofu with minimal, if any, changes. Its purpose is to provide a reliable, transparent, and community-governed alternative for defining and managing infrastructure as code.

What is Infrastructure as Code (IaC)?

Infrastructure as Code (IaC) is the practice of managing and provisioning computing infrastructure (like networks, virtual machines, load balancers) using configuration files rather than manual processes or interactive tools.

The core idea is to treat infrastructure definitions in the same way software developers treat application code.

This includes using version control systems (like Git), employing automated testing, and integrating with continuous integration/continuous deployment (CI/CD) pipelines.

The benefits of IaC are substantial:

  • Consistency: Eliminates “drift” between environments, ensuring that development, staging, and production environments are identical.
  • Speed: Accelerates the provisioning of new environments or the scaling of existing ones, moving from days or weeks to minutes.
  • Reliability: Reduces human error, as the process is automated and repeatable.
  • Accountability: Changes to infrastructure are tracked through version control, providing an audit trail.
  • Cost Efficiency: By provisioning resources only when needed and de-provisioning them when not, it helps optimize cloud spending.

Why OpenTofu?

While Terraform has been a dominant player in the IaC space, the community fork to OpenTofu arose from concerns about the licensing changes implemented by HashiCorp. OpenTofu maintains the open-source spirit, ensuring that the tool remains freely available and governed by the community. For practical purposes, it offers the same powerful capabilities as Terraform for interacting with various cloud providers (AWS, Azure, GCP, etc.) and other infrastructure platforms.

OpenTofu uses a declarative language called HashiCorp Configuration Language (HCL), which allows you to describe the desired state of your infrastructure. You define what you want your infrastructure to look like, and OpenTofu figures out how to get there, including managing dependencies and state.

“`hcl

Example OpenTofu configuration for an AWS S3 bucket

resource “aws_s3_bucket” “my_application_bucket” {

bucket = “my-unique-app-data-bucket-12345”

acl = “private”

tags = {

Environment = “development”

Project = “my-awesome-app”

}

}

resource “aws_s3_bucket_versioning” “versioning_enabled” {

bucket = aws_s3_bucket.my_application_bucket.id

versioning_configuration {

status = “Enabled”

}

}

“`

This simple HCL defines an S3 bucket in AWS, sets its access control list, tags it for organization, and enables versioning. OpenTofu interprets this configuration, communicates with the AWS API, and provisions these resources.

Key OpenTofu Commands

Working with OpenTofu involves a standard workflow:

  • tofu init: Initializes a working directory containing OpenTofu configuration files. This command downloads necessary provider plugins (e.g., for AWS, Azure, Google Cloud) that allow OpenTofu to interact with the respective cloud APIs. It’s usually the first command you run in a new or cloned OpenTofu project.
  • tofu plan: Generates an execution plan. This command analyzes your configuration files and the current state of your infrastructure (fetched from the cloud provider, compared with the state file), then determines what actions are necessary to achieve the desired state. It shows you exactly what OpenTofu will create, modify, or destroy before any changes are actually applied. This is crucial for reviewing changes and preventing unintended consequences.
  • tofu apply: Executes the actions proposed in a tofu plan. This command provisions or updates your infrastructure resources in the specified cloud provider. It typically prompts for confirmation before proceeding, although in automated pipelines, this confirmation is often bypassed using the -auto-approve flag.
  • tofu destroy: Destroys the infrastructure managed by the current OpenTofu configuration. This command is used to tear down entire environments or specific resources. Like apply, it’s recommended to review the plan before destruction and can be auto-approved in CI/CD.

GitHub Actions: Orchestrating Your Provisioning Pipeline

GitHub Actions is a CI/CD platform that allows you to automate tasks directly within your GitHub repositories. It enables you to build, test, and deploy code right from your Git workflow. When combined with OpenTofu, GitHub Actions provides a powerful way to automate the entire lifecycle of your cloud infrastructure.

What are GitHub Actions Workflows?

A GitHub Actions workflow is an automated process composed of one or more jobs.

These jobs consist of steps that can run commands, scripts, or pre-built actions. Workflows are defined using YAML files (.yml or .yaml) stored in the .github/workflows directory within your repository.

Key components of a GitHub Actions workflow:

  • name: A user-friendly name for your workflow.
  • on: Defines the events that trigger the workflow (e.g., push to a branch, pull_request opened, workflow_dispatch for manual triggering).
  • jobs: A collection of named jobs that run in parallel by default, or sequentially if dependencies are defined.
  • runs-on: Specifies the type of runner (e.g., ubuntu-latest, windows-latest) the job will run on.
  • steps: A sequence of tasks within a job. Each step can execute a shell command (run) or use a pre-built action (uses).
  • uses: References a reusable action from the GitHub Marketplace or a private repository.

    Actions can perform common tasks like checking out code, setting up environments, or interacting with cloud providers.

  • env: Defines environment variables for a job or a step.
  • secrets: Securely stores sensitive information (like API keys, cloud credentials) that can be accessed by workflow runs.

Why use GitHub Actions for OpenTofu?

Integrating OpenTofu with GitHub Actions offers numerous advantages:

  • Version Control: Your infrastructure code (OpenTofu HCL) and your automation logic (GitHub Actions workflow YAML) live side-by-side in the same Git repository, under version control.
  • Automated Triggers: Workflows can be automatically triggered on events like pushes to specific branches (e.g., main), creation of pull requests, or even on a schedule.
  • Collaboration & Review: Infrastructure changes are proposed via pull requests. This allows team members to review OpenTofu plan outputs and approve changes before they are applied to production environments.
  • Consistency: The same automated process is followed every time, reducing variability and ensuring consistent deployments.
  • Audit Trail: Every workflow run provides a detailed log of the OpenTofu commands executed, the outputs, and who triggered the run, offering a clear audit trail for compliance.
  • Secrets Management: GitHub Secrets provides a secure way to store sensitive credentials required by OpenTofu (e.g., AWS access keys, Azure service principal credentials).

Setting Up Your OpenTofu and GitHub Actions Project

Let’s walk through the practical steps to set up a project that leverages OpenTofu for infrastructure provisioning and GitHub Actions for automation. We’ll assume a basic understanding of Git and GitHub.

Project Structure

A typical project structure for OpenTofu with GitHub Actions might look like this:

“`

my-cloud-infra/

├── .github/

│ └── workflows/

│ └── provision-infra.yml

├── environments/

│ ├── dev/

│ │ └── main.tf

│ │ └── variables.tf

│ │ └── outputs.tf

│ │ └── terraform.tfvars

│ ├── staging/

│ │ └── main.tf

│ │ └── variables.

tf

│ │ └── outputs.

tf

│ │ └── terraform.tfvars

│ └── prod/

│ └── main.tf

│ └── variables.tf

│ └── outputs.tf

│ └── terraform.tfvars

├── modules/

│ ├── s3-bucket/

│ │ └── main.tf

│ │ └── variables.tf

│ │ └── outputs.tf

│ ├── ec2-instance/

│ │ └── main.tf

│ │ └── variables.tf

│ │ └── outputs.tf

├── .gitignore

├── README.md

└── main.tf (optional, for root level shared resources)

“`

  • .github/workflows/: Contains your GitHub Actions workflow YAML files.
  • environments/: Holds environment-specific configurations. Each subdirectory (dev, staging, prod) would typically contain the same OpenTofu code but with different variable values (terraform.tfvars) to define environment-specific settings (e.g., instance sizes, database tiers, region).
  • modules/: Contains reusable OpenTofu modules. Modules encapsulate common infrastructure patterns (e.g., an S3 bucket with standard settings, a hardened EC2 instance) and promote code reusability and maintainability.
  • .tf files: These are your OpenTofu configuration files (HCL).
  • .tfvars files: These files contain variable values specific to an environment or deployment.

Cloud Provider Authentication (AWS Example)

For OpenTofu to provision resources, it needs credentials to authenticate with your cloud provider. For AWS, the most secure and recommended approach for CI/CD environments is to use OpenID Connect (OIDC) to assume an IAM role. This avoids storing long-lived access keys directly in GitHub Secrets.

  1. Create an IAM OIDC Provider: In your AWS account, navigate to IAM, then Identity Providers, and add a new OpenID Connect provider. The Provider URL will be https://token.actions.githubusercontent.com and the Audience will be sts.amazonaws.com.
  2. Create an IAM Role: Create a new IAM role.
  • Choose ‘Web Identity’ for the trusted entity.
  • Select the OIDC provider you just created.
  • For the ‘Audience’, select sts.amazonaws.com.
  • Add a ‘Condition’ to restrict access. For example, StringLike: token.actions.githubusercontent.com:sub with value repo:your-org/your-repo:*. This ensures only workflows from your specific repository can assume this role.
  • Attach the necessary permissions policies to this role (e.g., AmazonS3FullAccess, AmazonEC2FullAccess, or more granular permissions based on your needs).
  • Note down the ARN of this IAM role.

GitHub Secrets Configuration

Even with OIDC, you might need some secrets, especially if not using AWS or for specific use cases.

In your GitHub repository:

  • Go to Settings -> Secrets and variables -> Actions.
  • Click New repository secret.
  • For AWS with OIDC, you primarily need to store the AWS_REGION if you want it configurable, or other non-sensitive configuration values. If you were not using OIDC (less secure, not recommended for production), you would store AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY here.

For our OIDC setup, we might set an AWS_REGION secret, or simply define it within the workflow.

OpenTofu Remote State Backend

By default, OpenTofu stores its state locally in a terraform.tfstate file. In a team or automated environment, this is problematic. You need a remote backend to store the state securely and allow multiple users/processes to work with the same infrastructure without conflicts.

Common remote backends include:

  • AWS S3 + DynamoDB (for locking)
  • Azure Blob Storage + Azure Storage Account Container (for locking)
  • Google Cloud Storage

Example backend.tf for AWS S3:

“`hcl

In environments/dev/main.tf or a dedicated backend.tf file

terraform {

backend “s3” {

bucket = “my-opentofu-state-bucket-12345” # Replace with your unique bucket name

key = “environments/dev/terraform.tfstate”

region = “us-east-1”

dynamodb_table = “my-opentofu-state-lock” # Replace with your DynamoDB table name for locking

encrypt = true

}

}

“`

You’ll need to create the S3 bucket and DynamoDB table manually once, or use a separate OpenTofu configuration to provision them (often called a “bootstrap” configuration). The DynamoDB table should have a primary key named LockID.

In the realm of cloud infrastructure management, the integration of automation tools has become increasingly vital for efficiency and scalability. A related article discusses enhancing content creation through advanced SEO techniques, which can be beneficial for developers looking to optimize their documentation and project visibility. You can explore this insightful piece on SEO optimization strategies by visiting this link. By leveraging such techniques alongside automation tools like OpenTofu and GitHub Actions, teams can ensure that their cloud provisioning processes are not only streamlined but also well-documented and easily discoverable.

Building the GitHub Actions Workflow

Metric Description Value Unit
Provisioning Time Average time to provision cloud infrastructure using OpenTofu and GitHub Actions 8 minutes
Deployment Frequency Number of infrastructure deployments per week 15 deployments/week
Automation Coverage Percentage of infrastructure provisioning automated 95 %
Error Rate Percentage of failed provisioning runs 2 %
Rollback Time Average time to rollback infrastructure changes 5 minutes
Cost Optimization Estimated reduction in cloud resource costs due to automation 20 %
CI/CD Integration Number of GitHub Actions workflows integrated with OpenTofu 3 workflows

Now, let’s create the GitHub Actions workflow file (.github/workflows/provision-infra.yml) to automate our OpenTofu deployments.

Workflow for Pull Request (Plan)

This workflow will run tofu plan whenever a pull request is opened or updated, providing a preview of infrastructure changes.

“`yaml

.github/workflows/provision-infra.yml

name: OpenTofu Infrastructure Plan

on:

pull_request:

branches:

  • main # Or your main branch name

paths:

  • ‘environments/**’ # Only trigger if changes are in environment configs
  • ‘modules/**’

env:

TF_WORKING_DIR: environments/dev # Default to dev for planning, can be dynamic later

jobs:

tofu_plan:

runs-on: ubuntu-latest

permissions:

id-token: write # Required for OIDC authentication

contents: read

pull-requests: write # To comment the plan on the PR

steps:

  • name: Checkout repository

uses: actions/checkout@v4

  • name: Configure AWS Credentials

uses: aws-actions/configure-aws-credentials@v4

with:

role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsOpenTofuRole # Replace with your IAM Role ARN

aws-region: us-east-1 # Or retrieve from a secret

  • name: Setup OpenTofu

uses: opentofu/setup-opentofu@v1

with:

tofu-version: 1.6.0 # Specify your desired OpenTofu version

  • name: OpenTofu Init (Plan)

id: init_plan

run: tofu init -backend-config=”key=${{ env.TF_WORKING_DIR }}/terraform.tfstate”

working-directory: ${{ env.TF_WORKING_DIR }}

  • name: OpenTofu Plan

id: plan

run: |

tofu plan -no-color -out=tfplan.binary > plan_output.txt

TOFU_PLAN_OUTPUT=$(cat plan_output.txt)

echo “$TOFU_PLAN_OUTPUT”

echo “TOFU_PLAN_OUTPUT<> $GITHUB_OUTPUT

echo “$TOFU_PLAN_OUTPUT” >> $GITHUB_OUTPUT

echo “EOF” >> $GITHUB_OUTPUT

working-directory: ${{ env.TF_WORKING_DIR }}

  • name: Comment OpenTofu Plan on PR

uses: actions/github-script@v6

if: github.event_name == ‘pull_request’

with:

script: |

const output = #### OpenTofu Plan for \${{ env.TF_WORKING_DIR }}\\n\n\\\\n${{ steps.plan.outputs.TOFU_PLAN_OUTPUT }}\n\\\“;

github.rest.issues.createComment({

issue_number: context.issue.number,

owner: context.repo.owner,

repo: context.repo.repo,

body: output

});

“`

Explanation:

  • on: pull_request: Triggers on PR activity.
  • env: TF_WORKING_DIR: Sets a default working directory. You could make this dynamic, e.g., by parsing PR labels or commit messages.
  • permissions: Crucial for OIDC (id-token: write) and for commenting on the PR (pull-requests: write).
  • aws-actions/configure-aws-credentials@v4: Action to set up AWS credentials using OIDC, assuming the specified IAM role.
  • opentofu/setup-opentofu@v1: Action to install OpenTofu on the runner.
  • tofu init: Initializes OpenTofu, specifying the backend configuration (key) dynamically based on TF_WORKING_DIR. This ensures each environment’s state is isolated.
  • tofu plan: Generates the plan. We use -no-color for better readability in plain text and redirect output to a file. The output is then captured and set as a workflow output.
  • actions/github-script@v6: Used to post the OpenTofu plan output as a comment on the pull request, making it easy for reviewers to see the proposed infrastructure changes.

Workflow for Push to Main (Apply)

This workflow will run tofu apply only after changes have been merged into the main branch, typically after a successful review of the plan.

“`yaml

.github/workflows/provision-infra.yml (continued, add as a new job or separate file)

name: OpenTofu Infrastructure Apply

on:

push:

branches:

  • main # Trigger on push to main

paths:

  • ‘environments/**’
  • ‘modules/**’

env:

TF_WORKING_DIR: environments/prod # Or staging, depending on your branch strategy

jobs:

tofu_apply:

runs-on: ubuntu-latest

environment: production # Or staging. Links to GitHub Environments for protection rules

permissions:

id-token: write

contents: read

steps:

  • name: Checkout repository

uses: actions/checkout@v4

  • name: Configure AWS Credentials

uses: aws-actions/configure-aws-credentials@v4

with:

role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsOpenTofuRole # Replace with your IAM Role ARN

aws-region: us-east-1

  • name: Setup OpenTofu

uses: opentofu/setup-opentofu@v1

with:

tofu-version: 1.6.0

  • name: OpenTofu Init (Apply)

run: tofu init -backend-config=”key=${{ env.TF_WORKING_DIR }}/terraform.tfstate”

working-directory: ${{ env.TF_WORKING_DIR }}

  • name: OpenTofu Apply

run: tofu apply -auto-approve

working-directory: ${{ env.TF_WORKING_DIR }}

“`

Explanation:

  • on: push: Triggers when code is pushed to the main branch.
  • env: TF_WORKING_DIR: Set to environments/prod (or staging) to ensure the correct environment is targeted for production deployments. You’d typically have separate workflows or conditional logic for different environments.
  • environment: production: This links the job to a GitHub Environment. GitHub Environments allow you to configure protection rules (e.g., manual approval, required reviewers) before deployments to sensitive environments. This is a crucial security measure.
  • tofu apply -auto-approve: The -auto-approve flag is essential for automation in CI/CD. It bypasses the interactive confirmation prompt. This is safe because the tofu plan was reviewed in the pull request phase.

More Advanced Workflow Considerations

  • Conditional Environments: You might use different workflows for different branches (e.g., dev branch deploys to dev environment, main branch deploys to prod). Or, use a single workflow with conditional logic based on the github.ref_name to set TF_WORKING_DIR dynamically.
  • Modularization: Leverage OpenTofu modules extensively. This keeps your environment configurations (environments/dev/main.tf) very lean and reusable, simply calling modules with environment-specific variables.
  • tofu validate and tofu fmt: Add steps to validate your OpenTofu code (tofu validate) and format it (tofu fmt -check -recursive) to maintain code quality before planning or applying.
  • Drift Detection: Consider adding a scheduled workflow that runs tofu plan regularly and notifies you if unexpected changes (drift) are detected in your infrastructure.
  • Destroy Workflow: Implement a separate, highly controlled workflow (e.g., manual trigger with strict approvals) for tofu destroy operations for specific environments.
  • Testing: For more complex infrastructure, you might integrate infrastructure testing frameworks like Terratest within your workflow to validate deployed resources.

In the realm of cloud computing, the integration of automation tools has become essential for efficient infrastructure management. A related article discusses the innovative use of automation in cloud environments, which complements the insights provided in “Automating Cloud Infrastructure Provisioning Using OpenTofu and GitHub Actions.” For further exploration of technology advancements, you can check out this informative piece on technology news and reviews at Enicomp. This resource offers a broader perspective on how automation is shaping the future of IT infrastructure.

Best Practices and Considerations

Automating cloud provisioning brings immense benefits, but it also introduces new considerations. Adhering to best practices ensures your automated infrastructure deployments are secure, reliable, and maintainable.

Security First

  • Least Privilege: Ensure the IAM role (or service principal) assumed by GitHub Actions has only the minimum necessary permissions to perform its OpenTofu operations. Avoid granting broad administrative access.
  • OIDC for Authentication: Always prefer OpenID Connect (OIDC) for authenticating GitHub Actions with cloud providers over static long-lived access keys. It eliminates the need to store sensitive credentials directly in GitHub Secrets.
  • GitHub Environments Protection Rules: Utilize GitHub Environments to enforce manual approvals, required reviewers, or specific branch protections before sensitive deployments (e.g., to production).
  • Secrets Management: Store all sensitive data (API keys, database passwords, etc.) as GitHub Secrets, and ensure they are only exposed to jobs that absolutely require them. Never hardcode secrets in your OpenTofu configurations or workflow files.
  • State File Security: Ensure your remote state backend (e.g., S3 bucket) is properly secured with strong access controls, encryption at rest and in transit, and versioning enabled to prevent accidental data loss.

Code Quality and Maintainability

  • Modularize OpenTofu Code: Break down your infrastructure into reusable OpenTofu modules. This promotes DRY (Don’t Repeat Yourself) principles, improves readability, and simplifies maintenance.
  • Consistent Naming Conventions: Establish clear and consistent naming conventions for your resources, variables, outputs, and files.
  • Version Control Everything: Treat your OpenTofu configurations and GitHub Actions workflows as critical code. Store them in Git, use branches for new features or changes, and leverage pull requests for review.
  • Document Your Infrastructure: Provide clear README.md files for your overall project and individual modules, explaining what they provision, how to use them, and any important considerations.
  • Regular Code Reviews: All infrastructure changes should go through a code review process, similar to application code. The tofu plan output posted on a PR is invaluable for this.

State Management

  • Remote Backend: Always use a remote backend for your OpenTofu state. Local state files are suitable only for isolated personal experiments.
  • State Locking: Ensure your remote backend supports state locking (e.g., DynamoDB for S3) to prevent concurrent OpenTofu runs from corrupting your state file. GitHub Actions inherently tries to avoid concurrent runs on the same branch for the same workflow, but explicit locking is a good safeguard.
  • State File Backups & Versioning: Your remote backend (like S3) should have versioning enabled for the state file itself, providing a history of your infrastructure’s state and allowing for easy rollbacks if needed.

Workflow and Deployment Strategy

  • Branching Strategy: Implement a clear branching strategy (e.g., GitFlow, GitHub Flow) that aligns with your environment promotion model (e.g., dev branch -> dev environment, main branch -> staging -> prod).
  • Separate Environments: Maintain distinct OpenTofu configurations and state files for each environment (dev, staging, production). Never mix environments in a single state file.
  • Review tofu plan Output: Make the tofu plan output a mandatory part of your pull request review process. This is your chance to catch unintended changes before they are applied.
  • Idempotency: OpenTofu is inherently idempotent, meaning applying the same configuration multiple times will result in the same desired state without unintended side effects. Trust this property.
  • Rollback Strategy: While tofu apply can revert changes if you revert the code, have a manual or automated rollback strategy in place for critical production issues. This might involve reverting to a previous Git commit and reapplying, or using cloud provider snapshots.
  • Monitor Workflow Runs: Regularly review the logs of your GitHub Actions workflow runs. Failed runs indicate issues that need immediate attention. Set up notifications for workflow failures.

By adopting these practices, you can build a robust, secure, and efficient automated cloud infrastructure provisioning system using OpenTofu and GitHub Actions, empowering your teams to deploy and manage cloud resources with confidence and speed.

FAQs

What is OpenTofu?

OpenTofu is an open-source tool that allows for automating the provisioning of cloud infrastructure using code.

How does OpenTofu work with GitHub Actions?

OpenTofu integrates with GitHub Actions to enable developers to automate the deployment of cloud infrastructure directly from their GitHub repositories.

What are the benefits of automating cloud infrastructure provisioning with OpenTofu and GitHub Actions?

Automating cloud infrastructure provisioning with OpenTofu and GitHub Actions helps streamline the deployment process, ensures consistency, reduces manual errors, and increases overall efficiency.

Can OpenTofu support multiple cloud providers?

Yes, OpenTofu is designed to support multiple cloud providers, allowing users to provision infrastructure on platforms such as AWS, Azure, Google Cloud, and more.

Is OpenTofu suitable for both small and large-scale projects?

Yes, OpenTofu is versatile and can be used for projects of varying sizes, making it a valuable tool for both small-scale applications and large enterprise projects.

Enjoying our content? Make us a preferred source on Google:

Add us as a Preferred Source on Google
Tags: No tags