Whale Tale, by Abigail Lynn

Happy new year! I hope you enjoyed the holidays (assuming you didn’t have to work) and are now back, eager to learn. I sure am and I’m back with a fantastic tutorial for you: how to use Docker for WordPress development. I actually announced it a few days ago via Twitter (in Catalan):

Let me start with a brief story: I started my journey as a WordPress developer using LAMP (Linux + Apache + MySQL + PHP). Unfortunately, I quickly realized that, when working on more than one project at the same time, the setup was a mess, as they all used the same server, the same database… I then moved to Vagrant (VVV in particular), an environment designed for WordPress development, but it was a pain in the ass: my 6-year old computer had a lot of trouble running multiple Virtual Box instances and everything was extremely slow. I then tried to move to Local by Flywheel, because Antonio seemed to enjoy the environment a lot… but it never arrived to Linux! Wasn’t there a fast, reliable, easy-to-use option for me?

In the end, I decided I had to learn more about Docker and see if this tool I’d heard of would fit my needs. If you want to learn more about Docker too and want to use it in your WordPress developments, don’t miss this guide – learn how to build development environments quickly and efficiently.


Docker is a computer program that performs operating-system-level virtualization, also known as “containerization”. This has two main advantages compared to the classic LAMP setup:

  • containers are isolated from each other and the communication between them is limited, which increases security
  • tools, libraries, config files, and everything the container needs to run is in the container itself, which means we’ll have a clean and tidy setup – once we’re done with the container, we can remove it and everything will be gone.

In my opinion, the main advantage Docker has over other virtualization softwares is the virtualization environment it uses by default: runC. This environment runs all containers on the host OS (instead of running a full virtual machine), which makes things way faster and more lightweight. That means the host (our PC) and the guest (the container) will share a lot of resources and everything will run like a charm in an “old” computer.

Docker Installation

To install Docker on Linux (Debian/Ubuntu), just run the following command:

Follow the instructions on screen and wait until apt is done.

Once Docker is installed, we can use it with the docker command. The only problem with Docker is the fact that you’ll need to run it with admin privileges using sudo. But we can solve this. If you’d rather run docker without sudo (and thus avoid typing your password often), add your user to the docker group:

where your-user is obviously your username in Linux. BTW, you might need to log out and log back in to actually apply this change.

Nelio Unlocker

Switch to WordPress safely while keeping your design and content

Improve your SEO today and boost your site speed by converting your pages into HTML, CSS, and WordPress standards. With zero technical knowledge required, you’ll only pay for what you need.

How to Install Docker on Mac or Windows

If you’re using Mac or Windows, you can also install Docker. Just follow the instructions on Docker’s website:

How to Use Docker

So, now that we have Docker set up and ready to be used, it’s time we give it a shot. As I said, Docker is a tool for managing contained apps. If we want to launch a container, we need to run the following command:

For example, try out this command:

to launch the classic Hello World within a Docker container. If everything worked as expected, you’ll first see something like this in your terminal:

This gives us some interesting information. First, Docker looked for an image named hello-world, but it couldn’t find it. Second, it looked for that image in an online image repository, and this time it succeeded and was able to download it. And that’s one of the great things about Docker: it has an image repository with several packed apps we can use as Docker containers (similar to what we have in WordPress, with the plugin and theme directories).

Now, back to our terminal, we can see the actual output of the Hello World container:

And that’s it! You’ve already run your first Docker container ?

If you now type the following command:

you’ll see the container Docker created using the hello-world image:

To remove it, just type:

making sure that you type in the appropriate CONTAINER ID.

And that’s basically all there is to Docker… So let’s jump into yet another tool (Docker Compose) and learn how to use it as WordPress developers ?

Docker Compose

As you can imagine, in Docker’s image repository there’s a WordPress image available. You might think that we simply need to pull that image and we’d be able to start a new WordPress site, but that’s actually not true:

  • On the one hand, this image doesn’t include the database system. So, if we want to start our WordPress site, we’ll need to install MySQL somewhere (or use a MySQL container).
  • On the other hand, the WordPress image requires some configuration parameters (such as, for instance, where’s the database and the credentials to access it). So everytime we run the instance, we need to specify those arguments.

Using the docker command alone can be complicated, specially when we have to start multiple containers all at once and some depend on others (as WordPress does). Luckily, there’s a tool called docker-compose that uses a configuration file to describe all the containers we need, the dependencies they have with each other, and their specific setup.

Let’s start by creating a new directory where Docker Compose’s config file will reside. For example, create ~/docker/test and add a file named docker-compose.yml with the following content:

I know this file might seem complicated, but it isn’t. Look carefully: the file simply defines the two services (or containers, if you will) that we need to run WordPress: mysql and wordpress. The former (mysql) contains information about the image we need (which is obviously a MySQL image, version 5.7) and some config parameters. The most relevant are probably the port mapping (we’ll be able to access the database using port 8081 in our host, which maps to 3306 in the guest) and the database setup itself (user, password, and so on).

The latter is WordPress itself (wordpress) and it follows a similar approach. Here we tell Docker Compose that we want WordPress to be accessible via the port 8080 in our host. We also specify that WordPress relies on the other service: mysql. And we finally add some additional configuration parameters (essentially, those to access the database).

Once we’re done, save and exit the file, and type the following command:

and Docker will download the WordPress and MySQL images and will start our WordPress.

Just wait for a couple of minutes and, once it’s done, go to http://localhost:8080 with your web browser (notice how the port we’re using here matches the one we specified in the configuration file) and you’ll see your new WordPress site:

Installing WordPress on a Docker Container
Installing WordPress on a Docker Container.

From this moment on, if we want to create new WordPress instances, you’ll simply have to repeat the steps we’ve discussed. That is, you’ll create a new directory in ~/docker/, you’ll add the docker-compose.yml file, and you’ll set it up. Just make sure that you’re using different ports for this new instance, or you won’t be able to run them both at the same time (for example, use 8082 for WordPress and 8083 for MySQL).

Once you’re done, you can stop the container running the following command:

Just make sure you use stop instead of down:

as down will not only stop the container, but also remove it completely. That is, if you restart a container using docker-compose up after you shut it down using the down command, your WordPress will start from scratch (an empty database, an uninstalled WordPress, etc).

Docker and WordPress Development

Great! You almost have all you need to develop WordPress plugins and themes using Docker effectively. I hope my explanations so far have been helpful and you now better understand what Docker is and how it works.

In my opinion, there are only two things missing to be 100% effective with this setup:

  • How to add your plugin or theme in the Docker container.
  • How to use domain names like http://content.local instead of http://localhost:port with Docker.

Adding Your Project into a Docker Container

Every time I have to work on a new plugin or theme, I create a new Docker container. If you follow the guide so far, you’ve probably realized that the WordPress installation created by Docker is completely empty and doesn’t contain any of our plugins or themes. So, assuming I’m working on a new plugin, how do I add that plugin in the wp-content/plugins/ folder that lies in the container?

There are multiple options to expose directories from the container and make them available in our host machine. The one I like the most maps the directory of our project (and only that directory) to a directory in WordPress. Let’s see this with a concrete example.

Assume I’m working on my plugin with my laptop. This plugin is currently in ~/dev/nelio-content/. I obviously want this plugin to be included in my development environment, so that I can test it and see that all the changes I apply work as expected. The first thing I’ll do is add a docker-compose.yml file in my plugin. The content of said file will be an exact copy of what we’ve already seen, but with one extra option:

Essentially, we simply added a new directive named volumes and we specify the mapping we want. That is, we want the current directory (namely, our plugin’s directory, identified with a dot .) mapped to /var/www/html/wp-content/plugins/nelio-content (which is a directory in the WordPress container). Easy, right? After all, the syntax we’re using is the same we used for port mapping, but we’re applying it to directories now.

And that’s it! If we now start our container, we’ll see that our plugin is available in the Dashboard » Plugins screen.

Using Domain Names

I personally don’t like accessing development installations using localhost and a port. I think using domain names like http://content.local is way easier. So, how can we achieve that? Well, we need a few things…

First, we need to tell our computer that a certain domain name (for instance, content.local) is the computer itself. This is as easy as editing the /etc/hosts file and mapping this new domain name to the localhost IP:

Great! Right now http://content.local is exactly equivalent to http://localhost. But that’s not exactly what we want… What we want is to map http://content.local to http://localhost:8080. To do that, we need a proxy server that maps requests to the former address to the latter. This is also very easy to achieve. Just create a new folder named ~/docker/proxy with the following docker-compose.yml file:

Then, run docker network create proxy and start your new container using docker-compose up -d. This will create a network that all our WordPress projects will use (more on that in a minute) and install the proxy that’ll do all the magic.

Finally, we simply need to modify our WordPress container so that it automatically notifies the proxy about its existence:

Okay, so we’ve done a few things here. Let’s take a closer look at all the changes we applied:

  • In the WordPress environment section we added two new attributes: VIRTUAL_HOST and VIRTUAL_PORT. These are the attributes that our proxy will use to map the VIRTUAL_HOST to localhost:VIRTUAL_PORT.
  • Since we want our WordPress service to talk to our proxy, we need to make sure both containers are in the same network. This is pretty easy: we simply add a networks option in WordPress and include the frontend network (you can use whatever keyword you want). Then, at the end of the file, I added a few more rules. In particular, I added a networks section and specified that there’s an external network identified by the keyword frontend with the name proxy. Notice this external network is the same one we used in the proxy’s docker-compose.yml.
  • Finally, I want to make sure that WordPress and MySQL can talk to each other too, so I have to make sure they’re also in a common network. To do that, I added them both in another network named backend, which is internal and, therefore, nobody else can access.

And that’s it! You now know everything I know about this tool. I hope you enjoyed this guide and, if you did, please share it with your colleagues and let me know in the comment section below!

Featured Image by Abigail Lynn.

35 responses to “Local WordPress Development with Docker”

  1. Vladimir Ilic Avatar
    Vladimir Ilic

    Thank you very much David, you helped me a lot!

    Best regards!

    1. David Aguilera Avatar

      Awesome! I’m happy my post was helpful :-)

  2. akbar Avatar

    thanks for this great tutorial but I can’t access to content.local, please record a video for this tutorial

    1. David Aguilera Avatar

      Hey Akbar! Glad you liked the tutorial. We might record a video in the future; in the meantime, please make sure you followed all the instructions AND edited /etc/hosts to map content.local to

  3. Eli Avatar

    Thanks for the tutorial. I set up a second project using the same .yml file and now when I try navigating to wp-admin for either site i get “Error establishing a database connection” indicating the credentials in the wp-config are incorrect. If i navigate to localhost:8080 I get ERR_EMPTY_RESPONSE. I’ve tried taking down the instance, removing the containers and images and spinning them back up, even uninstalled and reinstalled docker with no success. Any suggestions?

    1. David Aguilera Avatar

      Hi Eli!

      If you want to run more than one Docker instance at the same time, keep in mind you have to tweak their yml files so that each instance uses their own ports (WordPress’ and MySQL’s, of course). Otherwise, they’ll collide and you’ll have trouble.

      Thus, one instance would be WordPress-8080:80 and MySQL-8081:3306 and the other one would be WordPress-8082:80 and MySQL-8083:3306.

      I hope this helps! Let me know if it worked or if you’re still stuck.

      1. diversityx Avatar

        Thanks for your tutorial! I however ran into the “error establishing database” using the secondary ports you offered. Could you please give me the actual lines? Is the db: replaced with the 8083? I don’t see the 8081 in your yml sheet.


        1. David Aguilera Avatar

          Well, this really depends on the ports you used when you configured both services in docker-compose.yml. For instance, in my examples I mapped MySQL 3306 guest port to the host’s 8081, and WordPress’ 80 guest port to host’s 8080. You can map guest ports to your preferred host port, as long as the given host port is free. Also, please take a look at this other post I published: Local WordPress Development with Lando (or Docker made easy).

  4. Maria Avatar

    Thank you for such in-depth, step-by-step instructions! Everything works when I access the site through localhost:8080, but, I am having trouble making a unique domain name. After some issues, restarted computer just to start afresh. When I run docker-compose up -d for the proxy yml file, I get:

    ERROR: Network proxy declared as external, but could not be found. Please create the network manually using docker network create proxy and try again.

    So, I run docker network create proxy and it generates a huge string. Cool. I’m assuming this means the proxy is running? Then I do docker-compose up -d for the main file which contains the wordpress and sql containers. Running docker network ls -a shows me proxy with its own network id, but there is also a “MyFolderName”_backend (I think in your example it would be test_backend) also running with its own network id.

    I have updated the etc/hosts file to point to wordpress.test. Running wordpress.test in chrome gives me a “This site can’t be reached. Try checking connection/proxy/firewall ERR_CONNECTION_REFUSED”.

    BUT, going to localhost:8080 works. So, what am I missing? If it helps, I’m on Ubuntu 18.04 LTS. Trying to learn many things at once :) I’m usually on a mac.

    Your help would be very much appreciated!

    1. David Aguilera Avatar

      Hi Maria!

      You’re absolutely right, I forgot to mention that you first need to create the shared network (which, in my post, was named “proxy”). After running docker network create proxy you still need to (a) run docker-compose up -d on the container that will act as a proxy and (b) then run docker-compose up -d in your WordPress project.

      The behavior is actually pretty simple: when you start a docker container, everything is isolated in their own private network. In fact, if you were to list all the networks docker created (using docker network list) you’d see it created a bunch of them: one for every docker machine you started. However, we want to make sure that our WordPress site can use a “beautiful” domain name, so we need a tool (a proxy) that can redirect requests from the “beautiful” domain name to the actual WordPress container. This tool is another Docker container: jwilder/nginx-proxy. Now, since we want this proxy to be able to talk to all our WordPress sites, we simply tell Docker that our proxy uses a certain external network (conveniently named proxy) and so does our WordPress installation.

      So, to sum up:

      1. Create the proxy (you’ll only have to run this one)
      2. Start the proxy container
      3. Start your WordPress container

      This should do the trick.

      1. Maria Avatar

        Hello David! Thank you for you most thorough of answers. Seriously, whenever I go out looking for tutorials or try to troubleshoot errors, I read them thinking, “This is for someone with much more knowledge than I have. They’re assuming I know too much!” lol I’m new to all of this, so it’s refreshing to have so much guidance. I am having another issue… it starts at “BUT” below.

        I was getting into another error, which I fixed, but thought I’d mention in case someone else is having issues. So, when I ran “docker-compose up -d” on the proxy yml file I would get this:

        ERROR: for proxy_nginx-proxy_1 Cannot start service nginx-proxy: driver failed programming external connectivity on endpoint proxy_nginx-proxy_1 (#some-long-id-num): Error starting userland proxy: listen tcp bind: address already in use

        ERROR: for nginx-proxy Cannot start service nginx-proxy: driver failed programming external connectivity on endpoint proxy_nginx-proxy_1 (#another-long-id-num): Error starting userland proxy: listen tcp bind: address already in use
        ERROR: Encountered errors while bringing up the project.

        So, I thought this meant that 3306 is already being used and went ahead and changed it to 3307. In terminal, everything looks good and running!

        BUT, I’m getting “Error establishing a database connection” when I go to the unique url, which I see was also mentioned in another comment. The console in the window gives “Failed to load resource: the server responded with a status of 500 (Internal Server Error)”. I don’t have any other containers running, have double checked all the port numbers in the yml files, and the etc/hosts file is updated. Thoughts? What might have I missed?

        … I swear…. if it’s a semi colon or an extra space somewhere…..

        1. David Aguilera Avatar

          I’m glad you like our tutorials! We try to keep “all” audiences in mind when writing them.

          Regarding your issue, I guess it’s probably because you started your docker machines before everything was properly set up and know they’re having trouble finding each other. For example, as I mentioned in my previous response, you have to create an external network that the proxy and the WordPress service in your WordPress docker machine will share. You had already figured that out: you had to run docker network create to create it. The thing is, once the network is created, starting a docker machines will pull the network ID (that “long string” you mentioned) and use that… so it’s possible that your docker machines are not connected with each other.

          So here’s what I would do: destroy all your docker machines. First, run docker-compose down. Then, run a docker ps -a and remove all the machines using their IDs (docker remove ID1 ID2 ... IDn). Next list your networks: docker network list. You should only have the proxy network and the default one: none. Remove any other networks.

          Once you have a clean setup, start your proxy using docker-compose up -d. Finally, open your WordPress setup, make sure the ports are properly set (that is, map some random ports in your host, such as 8080 and 8081, to the guest ports, i.e., 80 and 3036). Basically, use the setup I shared in my post. And start this machine too: docker-compose up -d.

          And it should work!

          If it doesn’t, I’ll see if I can prepare a script that gets the environment up and running ;-)

  5. sunco Avatar

    Hola David. Estoy intentando entrar en el mundo de Docker y tengo unas dudas

    No se trata de código fuente ni configuraciones, o “como hacerlo”, solo dudas del tipo ¿se puede hacer … con Docker?

    1- Se pueden tener varias versiones de Php corriendo al mismo tiempo? Esto porque en mi maquina localhost tengo la versión mas nueva de Php y en la maquina de pruebas del trabajo tengo otra versión mas antigua. Aparte en producción tengo otra versión digamos intermedia en número de versión

    2- Se pueden tener varios proyectos con solo crear las carpetas? Por ejemplo var/www/html/proyecto1 y var/www/html/proyecto2 e indicarle con cual versión de Php corran

    3- Pienso instalarlo en un Amazon EC2 Linux.. se puede conectar con algún GUI en Windows para hacer las configuraciones/carpetas de manera mas sencilla?

    4- Hasta ahora no he encontrado algún tutorial de como mover ese Docker con todos sus proyectos/configuraciones a otro servidor

    Saludos desde Sinaloa, México

    1. David Aguilera Avatar

      ¡Hola! Muchas gracias por contactar con nosotros. Aunque no soy un experto en Docker (yo mismo estoy aprendiendo a usarlo y esta entrada es parte de mi aprendizaje), intentaré responderte.

      1. En principio sí. De la misma forma que puedes tener varias versiones de PHP dentro de un Linux, también puedes tenerlo en el Linux que hay corriendo dentro del Docker. Por otro lado, nada impide tener múltiples contenedores Docker corriendo, cada uno con setups diferentes (y, por lo tanto, con versiones de PHP diferentes).

      2. Sí, si cada carpeta corre en su propio contenedor Docker.

      3. No lo sé; supongo que no.

      4. Yo tampoco, pero no lo necesito ;-)

      Un saludo,

  6. Felix Avatar

    Hello, David: I really liked your tutorial, I was especially baffled, on how to create multiple instances of WordPress. You have cleared up my confusion, thanks.

    I do have a question about PHPMyAdmin, how would I add PHPMyAdmin to manage multiple databases? Is it necessary to have PHPMyAdmin?

    1. David Aguilera Avatar

      You obviously need a tool to manage your database. If you look at the docker-composer.yml file we created, you’ll see that we exposed the database in localhost:8081, which means you can connect to the database using your preferred application.

  7. Sanjog Lama Avatar
    Sanjog Lama

    Thanks for the helpful tutorial.

    I am able to access the Dashboard with the link localhost:8080 but how do I access the database/PHPMyAdmin. I am not able to access with this link localhost:8081.
    Check the screenshot – http://prntscr.com/pmocas

    1. David Aguilera Avatar

      PHPMyAdmin is not installed by default. You simply have access to the database via the 8081 port, but you need to install PHPMyAdmin somewhere else (maybe adding yet another service in Docker).

      1. organic Avatar

        I’ve added PHPMyAdmin as an additional service, but can’t access it yet, unfortunately.

        – mysql
        image: phpmyadmin/phpmyadmin
        container_name: phpmyadmin
        restart: always
        – ‘8082:80’
        PMA_HOST: mysql
        MYSQL_USER: user
        MYSQL_ROOT_PASSWORD: password
        – backend

        1. organic Avatar

          I’ve just recreated docker-compose and it worked out fine :)

          1. David Aguilera Avatar

            That’s awesome news! I’m glad you were able to do it ;-)

  8. organic Avatar

    Thank you for the remarkable tutorial, David.
    How can I properly associate a domain name with an EC2 instance? I’ve already added 4 NSs from Route 53 and defined URLs via wp-config.php in a Docker container, but it doesn’t work with Namecheap and Freenom domain name provider so far.

    1. David Aguilera Avatar

      Thanks for your kind words

  9. Son Goku ssj4 Avatar
    Son Goku ssj4

    Hi David, you’ve got one of the best tutorials on this topic. I kind of know what you wrote here but learned a few new tricks.

    The reason I ended on your site is because I googled “Local development WordPress workflow” hoping, I would find workflow:

    – Create local WP instance with docker
    – Modify it, add plugins, articles
    – This site then “migrate” to existing wordpress site on different host I paid online (not my own)

    I’m missing the link how to transfer my ‘site.local’ wordpress into ‘https://my-super-web.com’ (example). How would you do that? I tried copying files I mounted to folders from Docker but there were database errors, tried plugins, tried many other things.

    My ultimate goals is to “edit my site offline, when I’m happy, transfer it online” – simply.

    Best Regards

    1. David Aguilera Avatar

      Thanks, Goku! I’m a big fan of yours ;-)

      Now, back to your question… there are two important things you have to keep in mind: one the one hand, you have to configure your local site so that it uses the same domain name as your real site. This makes things easier, as all links included in your content and any other field values in your database will already have the appropriate URL when moving the database from one site to the other.

      On the other hand, to “push” your site to live, you can simply push all the folders to your live site. However, you have to keep in mind that wp-config.php has some information on how to connect to your database, and the setup between your local and live sites might differ… so don’t overwrite that particular file (or keep a local copy of the live version).

  10. Flora Avatar

    Thank you for such a great tutorial, this was really helpful!
    I do have one problem though, I have the local site running but the loading time is very slow. It takes forever for any local page to load (about 20 seconds). Is there something I overlooked with the setup or do you have any tips to solve this?

    1. David Aguilera Avatar

      Hi, Flora! Thanks for your kind words. Unfortunately, I’m afraid I don’t know what might be amiss. I’m using Docker on a Linux machine and, so far, it’s been running pretty smoothly… I’ve never faced such an issue and, therefore, I have absolutely no idea what might be amiss :-(

  11. Roland Avatar

    Thank you for the tutorial. I’m close but am still running into one pesky issue when using the proxy – I’m on OSX, and for now I’ve only found a way to connect via ports.
    Currently all urls work, except the root, e.g. http://mysite.local/phpinfo.php works, but http://mysite.local does not. Any idea what is causing this?

    1. David Aguilera Avatar

      Hi Roland, I’m glad my tutorial was helpful :-)

      What do you mean by “all URLs (but the root) work”? From your example, I see you’re simply accessing a specific PHP file you created in your root folder. Does your root folder have an index.php? If it does, I’d say it’s working… and then the problem is that WordPress is probably misconfigured. I need more info :-P

  12. Henry Avatar

    David, thank you for the article. Wondering if you can you explain is simple terms how do I access the volume in the container via my mac’s (the host) Finder (not terminal) so that I may view, sync, backup, and (boy oh boy) edit! the php files. Thanks again.

    1. David Aguilera Avatar

      Well, it’s actually quite easy: the volumes directory lets you map a folder in your host (e.g. Mac) to a folder in your guest (the VM). So all you need to do is map the project directory (.) to your theme/plugin (/var/www/html/wp-content/plugins/whatever). If, on the other hand, you want to have access to the full WordPress installation, then map the project directory to WordPress’ root (/var/www/html) and then simply unzip WordPress (which you can download from wordpress.org) in your project directory.

  13. Kerri Avatar

    First off, as so many others have already said, thanks for writing the most clear and explanatory intro to setting up WordPress in docker on the internets (especially explaining the hostname process)!

    I’ve had success at multiple tries of getting the WordPress environment running, but haven’t yet been able to get the custom hostname to work. I keep trying from scratch, removing all containers and networks, and then rebuilding in the order you described in Maria’s comment (using your template essentially identically, aside from changing the virtual hostname), however, each time I get this warning: “WARNING: Some networks were defined but are not used by any service: frontend, backend”, and the virtual hostname url does not work (502 Bad Gateway). Do you have any ideas of what I might be doing wrong?

    1. David Aguilera Avatar

      That’s weird… I don’t know what could be amiss. Before I try to replicate the issue, however, please let me redirect you to this other post: Local WordPress Development with Lando (or Docker made easy). I’ve been recently using Lando for my developments, because I find it a little bit easier and faster than Docker. Did you try it? Does it fit your needs?

      1. Kerri Avatar

        Thank you! I’ll look into it — I haven’t gone too deep down the Docker rabbit hole yet…

        1. David Aguilera Avatar

          You’re welcome. Please let us know what you find out :-)

Leave a Reply

Your email address will not be published. Required fields are marked *

I have read and agree to the Nelio Software Privacy Policy

Your personal data will be located on SiteGround and will be treated by Nelio Software with the sole purpose of publishing this comment here. The legitimation is carried out through your express consent. Contact us to access, rectify, limit, or delete your data.