Publi

Cómo utilizar PHP desde contenedores docker tanto de forma local como en producción

Una de las medidas más importantes que debemos tomar a la hora de realizar proyectos de programación, sobre todo en equipo, es asegurarte de que todos tienen el mismo entorno de trabajo. Si nos referimos a un entorno de aplicaciones en PHP, todos los miembros involucrados deberán tener la misma versión de PHP, las mismas extensiones y la misma configuración del sistema. Además, esas mismas configuraciones deberían ser las mismas en los entornos de test y producción, aunque en producción quitemos las herramientas de debug o depuración. Así, cuando hay un problema, podemos evitar en la medida de lo posible el famoso: «En mi ordenador funciona» cuando al subir cambios al servidor, o al probarlos un compañero, no funcionan.

Tenemos varias opciones al respecto. Podemos utilizar máquinas virtuales con soluciones como Vagrant. Otra opción es utilizar Docker para ejecutar PHP. Incluso podemos ejecutar PHP desde nuestra terminal a través de docker con aplicaciones como composer, o artisan de Laravel.

Dockerfile

Vamos a basarnos en las imágenes oficiales de php, en este caso php7.2. Y vamos a instalar por defecto extensiones como xml, zip, curl, gettext o mcrypt (esta última debemos instalarla desde pecl.
En los contenedores vamos a vincular /var/www con un directorio local, que puede estar por ejemplo, en la $HOME del usuario actual, donde podremos tener nuestros desarrollos. Y, por otro lado, la configuración de PHP también la vincularemos con un directorio fuera del contenedor, por si tenemos que hacer cambios en algún momento. En teoría no deberíamos permitir esto, la configuración debería ser siempre fija… pero ya nos conocemos y siempre surge algo, lo mismo tenemos que elevar la memoria en algún momento, cambiar alguna directiva o alguna configuración extra.

Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
FROM php:7.2-fpm
ARG HOSTUID
ENV BUILD_DEPS="autoconf file gcc g++ libc-dev make pkg-config re2c libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libpng-dev libssl-dev libc-client-dev libkrb5-dev zlib1g-dev libicu-dev libldap-dev libxml2-dev libxslt-dev libcurl4-openssl-dev libpq-dev libsqlite3-dev" \
    ETC_DIR="/usr/local/etc" \
    ETC_BACKUP_DIR="/usr/local/etc_backup"

RUN apt-get update && apt-get install -y less \
    procps \
    git \
    && pecl install redis \
    && pecl install xdebug \
    && docker-php-ext-enable redis xdebug \
    && apt-get install -y $BUILD_DEPS \
    && docker-php-ext-install -j$(nproc) iconv \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
    && docker-php-ext-install -j$(nproc) imap \
    && docker-php-ext-install -j$(nproc) bcmath \
    && docker-php-ext-install -j$(nproc) calendar \
    && docker-php-ext-install -j$(nproc) exif \
    && docker-php-ext-install -j$(nproc) fileinfo \
    && docker-php-ext-install -j$(nproc) ftp \
    && docker-php-ext-install -j$(nproc) gettext \
    && docker-php-ext-install -j$(nproc) hash \
    && docker-php-ext-install -j$(nproc) intl \
    && docker-php-ext-install -j$(nproc) json \
    && docker-php-ext-install -j$(nproc) ldap \
    && docker-php-ext-install -j$(nproc) sysvshm \
    && docker-php-ext-install -j$(nproc) sysvsem \
    && docker-php-ext-install -j$(nproc) xml \
    && docker-php-ext-install -j$(nproc) zip \
    && docker-php-ext-install -j$(nproc) xsl \
    && docker-php-ext-install -j$(nproc) phar \
    && docker-php-ext-install -j$(nproc) ctype \
    && docker-php-ext-install -j$(nproc) curl \
    && docker-php-ext-install -j$(nproc) dom \
    && docker-php-ext-install -j$(nproc) soap \
    && docker-php-ext-install -j$(nproc) mbstring \
    && docker-php-ext-install -j$(nproc) posix \
    && docker-php-ext-install -j$(nproc) pdo_pgsql \
    && docker-php-ext-install -j$(nproc) pdo_sqlite \
    && docker-php-ext-install -j$(nproc) pdo_mysql \
    && yes | pecl install "channel://pecl.php.net/mcrypt-1.0.1" \
    && { \
    echo 'extension=mcrypt.so'; \
    } > $PHP_INI_DIR/conf.d/pecl-mcrypt.ini \
    && echo "Fin de instalaciones"
COPY docker-entry.sh /usr/local/binx

RUN mv $ETC_DIR $ETC_BACKUP_DIR \
    && chmod +x /usr/local/bin/docker-entry.sh \
    && rm /etc/localtime

RUN useradd -s /bin/bash -d /var/www -u $HOSTUID user

ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]
CMD ["php-fpm"]

También tendremos un archivo docker-entry.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
echo "Iniciando contenedor"

VERSION="7.2"
CONFFILE=/etc/php/$VERSION/fpm/php-fpm.conf
DOCKERIP=$(hostname --ip-address)

if [ $(ls $ETC_DIR | wc -l) -eq 0 ]; then
        echo "Copiando configuración por defecto"
        cp -r "$ETC_BACKUP_DIR"/* "$ETC_DIR"
fi

/usr/local/bin/docker-php-entrypoint $@

Para construir la máquina podemos utilizar esto:

docker build -t myphp7.2-fpm --build-arg HOSTUID=»$(id -u)» --cpuset-cpus=»0-7″ .

Utilizo cpuset-cpus para delimitar los núcleos que vamos a utilizar para compilar los módulos. Esto puede tardar un poco y, si tenemos varios núcleos, puede interesarnos utilizar uno o dos, y mientras se construye PHP, utilizar el ordenador para navegar por Internet o algo así. Yo suelo crear un archivo build.sh con esa misma línea de antes.

Ahora, tendremos unos argumentos a la hora de lanzar el contenedor (run.sh)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
readonly WWWPATH="
$HOME/www"

GATEWAY="
"
if [ -n "
$(which dnsmasq)" ]; then
    if [ -z "
$(pidof dnsmasq)" ]; then
        sudo dnsmasq --bind-interfaces
    fi
    GATEWAY="
--dns $(ip addr show docker0 | grep -Po 'inet \K[\d.]+')"
fi

pushd $SCRIPTPATH > /dev/null
docker run --rm --name myphp7.2-fpm -v /etc/localtime:/etc/localtime:ro -v $WWWPATH:/var/www:rw -v $(pwd)/conf:/usr/local/etc/:rw $GATEWAY --user www-data --cpuset-cpus="
7" -d myphp7.2-fpm

Este archivo podremos reescribirlo dependiendo de nuestra configuración local. En mi ordenador, utilizo dnsmasq como dns en la máquina host, de forma que si modifico mi /etc/hosts, pueda acceder a dichos nombres desde mi contenedor PHP. Además, es conveniente editar en este archivo la variable WWWPATH donde estableceremos la ruta base desde la que tendremos todos nuestros archivos PHP, a partir de la que serviremos con FPM los archivos.

Configurando un servidor web

Este PHP con FPM debemos configurarlo en un servidor web, para ello utilizaremos proxy_fcgi dejando el VirtualHost más o menos así (no he puesto configuración de SSL porque estoy en local, aunque también podríamos configurarla):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<VirtualHost *:80>
    ServerName prueba_de_mi_web.local

    ServerAdmin webmaster@localhost
    Define webpath /prueba_de_mi_web.com
    DocumentRoot /home/gaspy/www/${webpath}
    <Directory /home/gaspy/www/${webpath}/>
            Options +FollowSymLinks
        AllowOverride all
    </Directory>

    <IfModule proxy_fcgi_module>
         ProxyPassMatch "^/(.*\.ph(p[3457]?|t|tml))$" "fcgi://myphp7.2-fpm.docker.local:9000/var/www/${webpath}/$1"
         DirectoryIndex index.html index.php
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Mi $HOME, en mi ordenador es /home/gaspy/ y en www almaceno todo el código de aplicaciones web. En mi /etc/hosts he hecho una entrada que apunta a la IP del contenedor. Para ello puedo utilizar este script que actualiza /etc/hosts con los dockers que hay en ejecución en este momento.

Ejecución desde línea de comandos

Una operación que realizo casi a diario en PHP es la ejecución de scripts desde la línea de comandos. Ya sea por una operación que se realiza en segundo plano, o ejecutar composer, o comandos propios de frameworks o plataformas como artisan de Laravel, occ de Owncloud, yii de su framework homónimo, etc.
Pero claro, para ejecutar los scripts, tengo que hacerlo desde dentro del contenedor, muchas veces con el usuario local y no como root, y generalmente los archivos a ejecutar se encontrarán en mi directorio local, pero dentro del contenedor estarán en /var/www. Además, tenemos que tener en cuenta que muchas veces ejecutaré código php de forma interactiva, es decir, ejecutando php y escribiendo el código, y otras veces haré algo así:

1
2
<?php
echo "Hola mundo!";

Para ello, tengo un script que ejecutará php (php7.sh):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

CURRENT_DIR="
$(pwd)/"
BASE_DIR="
$HOME/www/"
DOCKER_DIR="
/var/www/"
CONTAINER_NAME="
myphp7.2-fpm"

TRANSLATED_DIR="
${CURRENT_DIR/$BASE_DIR/$DOCKER_DIR}"

if [ -z "
$(docker ps | grep $CONTAINER_NAME)" ]; then
    $SCRIPTPATH/run.sh
fi

if [ "
$1" == "-u" ]; then
    shift
        NUID=$(id -u "$1")
    shift
elif [ "
$1" == "-l" ]; then
    shift
    NUID=$UID
fi

if [ -n "
$1" ]; then
    set -- "
${1/$BASE_DIR/$DOCKER_DIR}" "${@:2}"
    QUOTED_ARGS="
$(printf " %q" "$@")"
fi

if [ -n "
$NUID" ]; then
    USER="
$(getent passwd 1000 | cut -f1 -d:)"
    DIR="
${DOCKER_DIR}${USER}"
    if [ ! -d "
${BASE_DIR}$USER" ]; then
        docker exec -u root -i myphp7.2-fpm bash -c "
mkdir "$DIR"; chown $NUID:$NUID "$DIR""
    fi
    docker exec -u "
$NUID:$NUID" -i myphp7.2-fpm bash -c "HOME="$DIR"; cd $TRANSLATED_DIR 2>/dev/null; exec php ${QUOTED_ARGS}"
else
    docker exec -i myphp7.2-fpm bash -c "
cd $TRANSLATED_DIR 2>/dev/null; exec php ${QUOTED_ARGS}"
fi

A este archivo, le podemos crear un enlace dentro de /usr/local/bin/ llamado php para que podamos ejecutarlo desde cualquier sitio:

sudo ln -s $(pwd)/php7.sh /usr/local/bin/php

El script, lo que hace primero es averiguar el directorio actual desde donde ejecutas el script, y transformará dentro de esa cadena la ruta $BASE_DIR (donde están los archivos php en mi ordenador) por la ruta $DOCKER_DIR (donde están los archivos php en el contenedor). De esta forma si, fuera de docker ejecutamos:

php archivo.php

Dicho archivo se buscará en el directorio correspondiente dentro del contenedor. Eso sí, fuera de $BASE_DIR no podremos ejecutar archivos. Adicionalmente podremos ejecutar:

php -u www-data archivo.php

Para ejecutar el archivo.php como el usuario www-data o también
php -l archivo.php

Si queremos ejecutar el archivo php como el usuario actual. Además, este script se encarga de lanzar la máquina docker (con run.sh) si no está en ejecución actualmente.

composer y otros scripts parecidos

Composer utilizará el script anterior de php para ejecutarse. Aunque podemos tener un problema con la salida en color. Así que podemos imponerla. Por otro lado, queremos ejecutar composer como el usuario actual, en lugar del usuario www-data, ya que todo lo que instala será código y el código no debe poder ser sobreescrito por ese usuario (generalmente).

Así que podemos crear este script en /usr/local/composer:

1
2
#!/bin/bash
php -u "$(whoami)" /home/gaspy/www/composer --ansi $@

Algunas posibilidades

Siempre podemos meter la pata en la configuración, por un lado, si queremos recargar la configuración, tendremos que parar el contenedor y reiniciarlo:

docker stop myphp7.2-fpm
php

Como vemos, simplemente cargando php se iniciará el contenedor de nuevo. Además, si hemos metido la pata en la configuración, podemos eliminar los archivos del directorio conf y reiniciar el contenedor para que automáticamente se restaure la configuración por defecto.

También podemos incluir nuestra propia configuración en el Dockerfile para que todo nuestro equipo tenga los mismos archivos de configuración la primera vez nada más construir la máquina.
Foto principal: unsplash-logoLuca Bravo

También podría interesarte....

There are 52 comments left Ir a comentario

  1. tecasoft2 /
    Usando Google Chrome Google Chrome 72.0.3626.109 en Windows Windows NT

    Que GEnIOOOO»!! .. Ty me ayudaste un toquee, me recordaste como solucionar un problema, que tenia.

    https://www.tecasoft.com/
    y tambien Diseño Web

    Muchas Gracias!!!

  2. Locknar /
    Usando Google Chrome Google Chrome 76.0.3809.100 en Windows Windows NT

    Vaya por delante que soy un novato en Docker, así que lamento si la pregunta es a lo mejor absurda.
    El caso es que me he bajado una imagen que contiene wordpress con letsencrypt, pero la versión de php que utiliza es la 5.6 y necesito actualizarla a la 7.2.
    ¿Hay alguna forma de modificar esto desde dentro o desde fuera del contenedor?
    Gracias de antemano.
    Un saludo.

  3. novel online /
    Usando Google Chrome Google Chrome 81.0.4044.113 en Windows Windows NT

    It was fortunate and fortunate that I came to this site and read a lot of interesting information. It is really useful to read. I like so much. Thanks for sharing this great information.

  4. Sergio /
    Usando Google Chrome Google Chrome 83.0.4103.97 en Linux Linux

    Gracias, quisiera hacerlo con una verion de php la 5.4 o 5.6 pero me da errores, me podrias ayudar?, gracias.

  5. sukdulang biyaya lyrics /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Sharing an inspirational Hugot Kristiyano Bisaya through memes and stories.

  6. Arianna /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    I fully agree with you! I appreciate the useful information you provided. Roof Repairs Leatherhead

  7. Kristal /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    This is a really good post. I like this topic. There are numerous resources on this page. Roofers in Gillingham Kent

  8. Harry Jack /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    I am exploring this article on the internet and finally got it. Thank for sharing this content. Hooded Varsity Jacket Mens

  9. jasonwants /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Great effort! Thanks. We provide a variety of hair extension options that can give your hair the perfect volume and length. hair extensions cherry hill

  10. Yna /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Nice! This is a power story for me. Thanks a lot for sharing with us!
    photographers in miami florida

  11. Magenda Lim /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Nice! This is a power story for me. Thanks a lot for sharing with us!
    boston private jet charter

  12. Irine /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    That blog post was fantastic. I’ll very certainly return to your blog. Well done!
    business consulting firms fayetteville ar

  13. Harry Jack /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Very Informative the author praises this superior work. The costume is made more classy by a magnificently designed that can be seen at Jacketars. Bape Pink Hoodie

  14. Josh /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Some useful information was in your article. Thank you for sharing!
    chimney sweep worcester ma

  15. Tiara /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Nice! I’m truly enjoying your post.
    vinyl fence installation framingham ma

  16. HERRY /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    omg this website was so helpful i really like it
    Suicide Squad 2 Harley Quinn Jacket

  17. targkocse /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    I was lucky to find this website and learn a lot of interesting things from it. It’s a great thing to read. I like so much. Thanks for letting me know about this great information. snow rider

  18. Navel /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Hi! This is so informative, I really appreciate the whole statement.
    masonry contractors san antonio

  19. Hope /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Wow! great blog post! this is interesting I’m glad I’ve been drop here, such a very good blog you have I hope u post more! keep posting. Fencing Contractors Wodonga

  20. Sammantha /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Hi! This is so informative, I really appreciate the whole statement.
    boston personal trainer

  21. Darna /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    A good story! Thanks for sharing.
    pressure washing fayetteville ar

  22. amazon kindle coupon /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    This blog help me alot to slove my windows problem and thanka for share the information.

  23. Write For Us + Free guest post /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    This blog has been really helpful to me in solving my Windows-related issues.

  24. Shawn /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Awesome! Thanks for sharing this very interesting content, I like it.
    boston iron works

  25. drywall repair /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    The codes you’ve stated here are quite interesting. I’ll finish my drywall repair first and re-run your scripts to see how it works. Cheers!

  26. john /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Thank you for the information you provide on your blog post. Concreters Maitland

  27. Belsom /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Such a great and amazing website keep sharing more thank you so much for permitting me to comment kalima wall clock

  28. Kenneth Marcial /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    Sharing an inspirational Hugot Kristiyano Bisaya through memes and stories. lilim lyrics

  29. jennifer /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Store For all of your Superhero Outfit, Celerity Jackets And Leather Jackets! – online
    Shelly ‘Elle’ Evans

  30. jennifer004 /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Store For all of your Superhero Outfit, Celerity Jackets And Leather Jackets! – online
    Spider Man Leather Jacket

  31. Olivia Miller /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Thanks for sharing! Indeed a wonderful blog! Please keep on posting! Roof Leaks and Repair Contractor

  32. Julia Brown /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Adios! Thanks for sharing! More amazing blogs please. Amazing just like the services offered at best epoxy flooring for commercial business in Tempe

  33. Matthew J /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Fantastic post! Thank you so much for sharing! Amazing just like the services offered at professional concrete contractors

  34. Daniel B /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Thanks a bunch for sharing! Very interesting and amazing! 24hr best towing services company

  35. Charlotte Drywall Contractors /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    To construct a container and run PHP, use docker run. Simply add some volumes to the container. The paths to your code should be included in these volumes.

  36. Renters Insurance oklahoma /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Farmers Insurance Yukon Oklahoma – Morris Agency. Your Local Oklahoma Farmers Insurance Agency in Yukon Renters Insurance oklahoma

  37. cane corso for sale oklahoma /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Breeding World Class Corsos. The Best Cane Corso Breeders. cane corso for sale oklahoma

  38. Roofing Faversham /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    I appreciate all the fantastic stuff and the time you put into this.

  39. cash home buyer in new jersey /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    We Buy Houses for Cash Anywhere In the Tri-State Area Area, And At Any Price. Check Out How Our Process Works. We’re Ready To Give You A Fair Offer For Your House.

  40. Nick /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    We provide office cleaning and commercial cleaning services in the derby area.
    Office cleaners Derby

  41. john /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Thanks for the blog loaded with so much information. Stopping by your blog helped me to get what I was looking for
    Upholsterers near me

  42. accounting firms in nj /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Hess Financial Solutions is one of the best bookkeeping and accounting firms in NJ based in the U.S accounting firms in nj

  43. we buy houses philadelphia /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Whether you’re in dire need of cash or need to sell your house for whatever reason. We buy houses Philadelphia for cash. We are here to help! We are committed to making your experience with us fast and hassle-free! we buy houses philadelphia

  44. tree services in bucks county /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    We help beautify your outdoor space and keep your home safe through tree service tree services in bucks county

  45. abweb006 /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    I can’t really imagine that i found this post it’s so amazing.Thanks for a lot more idea that i expected from you!
    Pergola Builders Bendigo

  46. Fresno Masonry Construction Pros /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Thanks for this content. It’s a huge help for me to my newly built website.

  47. EZ Smart Home Automations /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Grateful to see and able to read this article. Thank you.

  48. Splash Media /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    As I am starting my business related to digital services, this content helps.

  49. Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Congratulations. I can see that many people happy of this blog and I am one of them. Thank you so much. Keep this kind of content.

  50. Optometry Oakland CA /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Keep the good work. Awesome article.

  51. Silver /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    also learn to build a PHP website and run them. electricians mitcham

  52. fence company okc /
    Usando Google Chrome Google Chrome 105.0.0.0 en Windows Windows NT

    Building High-Quality Fences for OKLAHOMA RESIDENTS SINCE 2000 fence company okc

Leave a Reply